Spotting patterns in the JDK — and when not to use one
The point of learning patterns is reading code, not writing it. You will spend far more of your career in somebody else's codebase than starting a new one, and the value of the vocabulary is that a file you have never seen becomes recognisable in thirty seconds.
A field guide
What to look for, and what it tells you:
| You see | Probably | So |
|---|---|---|
static method returning the class's own type |
factory method | check whether it caches or returns a subtype |
Builder inner class, methods returning this |
builder | the required fields are in the builder's constructor |
| a field of the same interface the class implements | decorator | look for what it adds, and what it forwards |
| a field of a different interface | adapter | the shape was wrong |
| an interface with one method, passed in | strategy | the algorithm varies |
final method calling abstract ones |
template method | the order is the design |
addXListener |
observer | find the unsubscribe |
getInstance() |
singleton | check for mutable state |
| a class that only delegates | probably a facade | or nothing, and it should go |
Reading the JDK
The standard library is the best pattern catalogue available, because every example in it is load-bearing.
// factory methods — and note none of them say which class you get
List.of("Priya", "Arjun")
Optional.of(subscriber)
LocalDate.parse("2026-09-01")
Integer.valueOf(5)
// decorators — same type in, same type out, stacked
new BufferedReader(new InputStreamReader(new FileInputStream(path)))
Collections.unmodifiableList(deliveries)
Collections.synchronizedMap(cache)
// adapters — one shape presented as another
Arrays.asList(array)
Path.of(uri)
// strategies — the algorithm as an argument
deliveries.sort(Comparator.comparing(Delivery::date))
stream.filter(d -> d.tiffins() > 2)
// template method — implement two, get twenty
class MyList extends AbstractList<String> { /* get, size */ }
// builder
StringBuilder sb = new StringBuilder().append("a").append("b");
Stream.of(1, 2, 3).map(...).filter(...).toList();
Read java.io once with this in mind. It stops being a pile of classes with
confusing names and becomes one idea applied consistently.
Spring, since you will meet it
The Java course after this one is Spring, and it is patterns end to end:
- Dependency injection everywhere — the container is the composition root from the singleton lesson, automated.
JdbcTemplate,RestTemplate,TransactionTemplate— template method, and the name says so.@Transactional,@Cacheable— decorators, applied by generating a proxy around your bean.ApplicationEventPublisher— observer, with@EventListeneras the subscribe.BeanFactory— a factory, which the name also says.
Spring is not a new set of ideas. It is these ideas with the wiring automated — which is why this module comes before it.
Recognising misuse
Patterns applied where they were not needed have a look to them.
An interface with one implementation, forever. A strategy that has never varied. Ask when the second implementation is expected; if the answer is vague, it is indirection.
A factory that only calls new.
public static Delivery create(LocalDate date, String customer, int tiffins) {
return new Delivery(date, customer, tiffins);
}
No name improvement, no caching, no subtype. Delete it and call the constructor.
Decorators stacked six deep, where no single file tells you what the object does and you have to follow six constructors to find out.
A "manager" or "helper" or "util" class with twenty unrelated methods. Not a facade — a drawer for things nobody wanted to name.
Abstract classes with one subclass. The abstraction was designed for a future that never arrived.
None of these means the original author was foolish. They usually mean the future they anticipated did not happen, which is the normal case. The right response is to simplify when you are in there for another reason, not to launch a refactoring project.
The rule worth keeping
Refactor to patterns, not with them.
Write the straightforward thing. When it becomes awkward — the third if/else
branch, the second caller wanting a variation, the method that now needs four
collaborators — the shape of the fix will usually be a pattern, and you will
recognise it.
Arriving that way means the pattern is solving a problem you actually have. It also means you can justify it in review, which is the practical test: if you cannot explain what varies, you do not need the pattern.
Check your work
Why patterns matter most for reading: you spend more time in code you did not write than in code you did.
What a class holding its own interface is: a decorator. A different interface: an adapter.
Why java.io looks the way it does: decorators, consistently.
What Spring is, in pattern terms: dependency injection, template method, decorators via proxies, and observer — with the wiring automated.
The signs of misuse: one implementation forever, a factory that only calls
new, six-deep decorators, a "helper" of twenty unrelated methods, an abstract
class with one subclass.
What misuse usually means: the anticipated future did not arrive, not that the author was foolish.
The rule: refactor to patterns, not with them.
The practical test: if you cannot say what varies, you do not need it.
Practice
- Open
java.ioin your IDE. List every class wrapping another of the same type, and say what each adds. - Find three static factory methods in the JDK that do not return the class you asked for.
- Read
AbstractListand list the methods implemented in terms ofgetandsize. - Find an interface with exactly one implementation in a project you did not write. Decide whether it earns its place.
- Find a factory method that only calls
new. Delete it locally and see whether anything is worse. - Take your capstone and name every pattern already in it, without adding any.
- Find a "Manager", "Helper" or "Util" class in an open-source project and count its unrelated responsibilities.
- Look at Spring's
JdbcTemplateJavadoc and identify the fixed steps and the varying ones. - Write down a time you added flexibility that was never used. What would have told you at the time?
- Explain to somebody why "we might need it later" is not a reason, and why it sometimes is.
Next: writing code that other people can read, which matters more than any of this.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship