Singleton and Dependency Injection
In short: Singleton guarantees a single instance of a class, while Dependency Injection passes dependencies in from outside, so the code can be tested with a substitute, such as a clock.
Problem
The test that passes only on Fridays
On Fridays the shop gives a 10% discount. The program has one clock, so it is a Singleton: a class with only one object, reachable from anywhere. The order service asks it what day it is:
class SystemClock { private static final SystemClock INSTANCE = new SystemClock();
private SystemClock() { }
public static SystemClock getInstance() { return INSTANCE; }
public LocalDate today() { return LocalDate.now(); }}
class OrderService { public double price(double amount) { if (SystemClock.getInstance().today().getDayOfWeek() == DayOfWeek.FRIDAY) { return amount * 0.9; } return amount; }}Someone writes a test to make sure the discount works:
OrderService service = new OrderService();System.out.printf("Friday discount: expected €90.00, got €%.2f%n", service.price(100));Run on Monday, October 26, 2026, the test prints:
Friday discount: expected €90.00, got €100.00On a Friday the same test prints 90.00. The test passes one day a week and fails on the other six, and the code is the same. To check the discount you have to wait for Friday or move the computer's clock.
The clock itself is done right. The problem is that price reaches for it itself. From outside you can't see that the service depends on a clock: new OrderService() asks for nothing. The test can't give it another day, because it can't give it anything.
The idea
The one object and handing it in
A Singleton guarantees two things: there is only one object, and anyone can take it from anywhere. The first is sometimes needed. The second is the price:
- a hidden dependency. Who uses the clock shows only if you read every line of every method;
- shared state. Whatever one piece of code changes in the object, everyone else sees, including the next test;
- it can't be swapped. The code points to the concrete class by name. Another clock for the test has no way in.
The fix is not to remove the clock, but to hand it in from outside. The service doesn't look for its dependency, it receives it in the constructor. Which clock it is, is decided by whoever creates the service: the program hands in the real one, and the test its own, with a date the test picks.
This is called Dependency Injection (DI). There are three roles:
- the service says what it needs: a parameter of an interface type in the constructor;
- the dependency is the object it is given: the real clock or the test one;
- the wiring happens in one place, at the start of the program. That is where you decide which object goes where.
The Singleton can even stay: the one clock is handed in like any other object. Nobody just reaches for it on their own.
See it
Singleton, the way the language gives it
if (instance == null). If two threads reach the check at the same time, you get two objects. The language has safer ways.The clock from the problem is created in a static final field. Java creates it once, when it loads the class, and that is safe with many threads too. If creating it is expensive and should happen only on the first request, the object goes into a nested class (a holder). Java loads Holder only on the first getInstance():class SystemClock { private SystemClock() { System.out.println("Creating the clock"); }
private static class Holder { static final SystemClock INSTANCE = new SystemClock(); }
public static SystemClock getInstance() { return Holder.INSTANCE; }}System.out.println("Start");SystemClock a = SystemClock.getInstance();SystemClock b = SystemClock.getInstance();System.out.println(a == b);StartCreating the clocktrueMore precisely, if you are curious
enum with one value, INSTANCE (you saw it in "Design patterns"). Java doesn't allow new for an enum and creates the value exactly once.See it
The clock comes in from outside
Clock: an interface with one method. SystemClock from the problem only adds implements Clock:interface Clock { LocalDate today();}
class OrderService { private final Clock clock;
public OrderService(Clock clock) { this.clock = clock; }
public double price(double amount) { if (clock.today().getDayOfWeek() == DayOfWeek.FRIDAY) { return amount * 0.9; } return amount; }}In the program, the real clock is handed in once, at creation:
OrderService service = new OrderService(SystemClock.getInstance());Now new OrderService(...) says out loud that it can't work without a clock. The dependency shows in the constructor's signature, not somewhere inside a method.
In big applications the wiring is done by a framework: Spring creates the objects and hands each one to whoever asks for it in their constructor. The idea is the same; by hand in main is for small programs.
See it
The test picks the day itself
The test needs a clock you can tell which date to return:
class FakeClock implements Clock { private LocalDate date;
public FakeClock(LocalDate date) { this.date = date; }
public void set(LocalDate date) { this.date = date; }
@Override public LocalDate today() { return date; }}The test hands it in instead of the real one:
FakeClock clock = new FakeClock(LocalDate.of(2026, 10, 30));OrderService service = new OrderService(clock);System.out.printf("Friday: €%.2f%n", service.price(100));
clock.set(LocalDate.of(2026, 10, 27));System.out.printf("Tuesday: €%.2f%n", service.price(100));Friday: €90.00Tuesday: €100.00FakeClock clock = new FakeClock(LocalDate.of(2026, 10, 30));OrderService service = new OrderService(clock);System.out.printf("Friday: €%.2f%n", service.price(100));
clock.set(LocalDate.of(2026, 10, 27));System.out.printf("Tuesday: €%.2f%n", service.price(100));Step by step, in words
- Before, price reached for SystemClock.getInstance() itself: a hidden link to a global object the test cannot change.
- The test makes its own clock: a FakeClock that says 2026-10-30, a Friday.
- The clock comes in from outside, through the constructor. OrderService only keeps a reference to it.
- price(100): the service asks its clock. It's a Friday, so the sum is 90.00.
- The test moves the date to 2026-10-27, a Tuesday. Nothing in OrderService changes.
- The same price(100): a Tuesday, 100.00. The test gives these two numbers on any day of the week.
The test gives these two numbers on a Monday, on a Friday and on a Sunday. OrderService is the same class that runs in the shop: the test changes only what it hands in.
More precisely, if you are curious
Clock has one method, so a test clock for a fixed day can also be a lambda: new OrderService(() -> LocalDate.of(2026, 10, 30)). A class pays off when the test moves the date, as here. Java also has a built-in java.time.Clock with Clock.fixed(...) for tests, and LocalDate.now(clock) asks it.Try it
Predict the output
The classes are from the cards before this one. October 31, 2026 is a Saturday. What does the program print?
FakeClock clock = new FakeClock(LocalDate.of(2026, 10, 30));OrderService shop = new OrderService(clock);clock.set(LocalDate.of(2026, 10, 31));OrderService app = new OrderService(clock);
System.out.printf("€%.2f%n", shop.price(50));System.out.printf("€%.2f%n", app.price(50));Try it
Find the bug
The cart is a Singleton, "so that there is one":
class Cart { private static final Cart INSTANCE = new Cart(); private final List<String> items = new ArrayList<>();
private Cart() { }
public static Cart getInstance() { return INSTANCE; }
public void add(String item) { items.add(item); }
public int size() { return items.size(); }}Two tests, each with its own checkout page. check prints OK or what the test expected:
CheckoutPage first = new CheckoutPage();first.add("Coffee");first.add("Mug");check("Test 1", first.count(), 2);
CheckoutPage second = new CheckoutPage();second.add("Coffee");check("Test 2", second.count(), 1);Test 1: OKTest 2: FAIL, expected 1, got 3Each test passes on its own. Together, the second fails. Which line has the bug?
Pick the line with the bug.
The page takes the one cart itself. Both pages, and both tests, fill the same object: the second test sees the coffee and the mug of the first. In the shop it is even worse: all customers shop in one cart. A cart is not something there is one of in the program; there is one per customer. The fix: the cart is an ordinary class with a public constructor, and the page receives it in its own: new CheckoutPage(new Cart()). Each test makes its own cart, and both pass.
More precisely, if you are curious
Test libraries run tests in an order that is not guaranteed, sometimes at the same time. A test that passes only when it runs first is a test that fails at random. Shared state in a Singleton is the most common cause.
The idea
When you don't need it
A Singleton makes sense when a second copy would be a mistake and the object keeps nothing that changes: settings read once at startup, the log, the pool of database connections. Even then it is better handed in from outside than reached for by everyone.
Handing in from outside has a price too. It's not worth it when:
- the dependency never changes and doesn't get in the way of tests.
Math.roundis not a dependency you hand in; - the program is small. Wiring by hand in main is enough; a DI framework is for big applications;
- the constructor asks for eight dependencies. That's not a DI problem: the class does too much.
Exercise
Black Friday
Solve the task in your editor.
Black Friday. On November 27, 2026 the shop gives a 30% discount, and on other Fridays still 10%. Add the rule to OrderService and check it with FakeClock for three dates: Black Friday, October 30 (a Friday) and October 27 (a Tuesday). The test gives the same output on any day.
Black Friday: €70.00Friday: €90.00Tuesday: €100.00More tasks
- Order numbers. The counter for order numbers is a Singleton and starts at 1001. Make it a dependency of
OrderServiceand write two tests that both get 1001 as the first number. - A cart for every customer. Fix the bug from "Find the bug" all the way: the cart is not a Singleton, and every page gets its own. Two customers add different items, and each sees only their own.
- Night discount. Between 22:00 and 6:00 the discount is 5%. The clock now has to give the hour too. What do you change in Clock, and how many tests do you need to be sure?
Test
Check yourself
Related lessons
Section titled "Related lessons"- Builds on: Nested classes · Lambdas · Design patterns · Strategy
- Continues in: Adapter and Facade · Repository · The shop: which pattern where · SOLID without dogma
- Tasks on this topic: Design patterns