Template Method Pattern
Learn to lock a fixed sequence of steps in a base class while letting subclasses fill in exactly one varying step, eliminating duplicated boilerplate across similar classes.
Learning objectives
- Explain why copy-pasted setup and teardown logic across similar classes is a maintenance risk
- Design an abstract base class with a locked orchestrating method and one abstract hook
- Explain why this pattern deliberately favors inheritance over composition
- Distinguish a mandatory abstract step from an optional hook with a default implementation
- Identify Template Method in HttpServlet, JUnit lifecycles, and Java's I/O and collection classes
◆ Story
A recipe template says: preheat the oven, prepare the filling — chicken or vegetable, your choice — assemble, bake for a fixed time, then let it rest. The overall sequence and timing are fixed and non-negotiable, but "prepare the filling" is deliberately left blank for the specific dish to fill in. The recipe's structure is locked; exactly one step is genuinely variable.
That's the shape of the Template Method pattern, and it's worth noticing upfront that it reaches for inheritance rather than composition, unlike most of the patterns built so far — a deliberate exception worth understanding, not a contradiction.
A set of file parsers makes the problem concrete. Building parsers for CSV and JSON — and realistically more formats later — every one of them needs to open a file, parse it according to its own format, and close the file. Two of those three steps are identical across every parser; only the middle one genuinely differs. Building it the obvious way first, one class per format with all three steps written out by hand, is what makes the fix worth adopting.
It's worth noticing the shape of the problem here is almost the mirror image of Strategy's. Strategy pulls an entire varying algorithm out into its own swappable object, leaving the calling class to hold nothing but a reference to it. Template Method keeps the algorithm's overall shape fixed in one place and only lets a small, specific piece of it vary — a difference in degree that ends up justifying a genuinely different mechanism.
The obvious first version: one class per format, each with a parse() method that opens the file, does its format-specific work, and closes the file. CsvParser.parse() calls a private openFile(), prints its CSV-specific parsing step, then calls a private closeFile(). JsonParser.parse() does the exact same thing, with the exact same openFile() and closeFile() bodies, just a different line in between.
Run both, and they work — printing exactly the expected sequence. This runs fine with two parsers. What happens with a fifth, or a tenth?
The exact same two methods are duplicated in every single parser class. openFile() and closeFile() aren't specific to CSV or JSON at all — they're identical, copy-pasted logic sitting in every parser that will ever be written. A fix to that shared logic has to be applied everywhere, correctly, by hand: if openFile() needs to also check that the file actually exists before proceeding, that change has to be copy-pasted into every parser class, and it's entirely possible to update some and miss others. Worse, it's possible to forget a step entirely — nothing enforces that a new parser class actually calls closeFile() at all, and a developer writing a tenth parser by hand could simply forget, with nothing in the language stopping them. This is exactly the "don't repeat yourself" problem: the same sequence of steps, reimplemented by hand in every new class, with real risk of drift or an outright missing step each time.
💻 Code example
class CsvParser { void parse() { openFile(); System.out.println("Parsing CSV file"); // the only genuinely different step closeFile(); } private void openFile() { System.out.println("Opening file"); } private void closeFile() { System.out.println("Closing file"); } } class JsonParser { void parse() { openFile(); // EXACT SAME CODE as CsvParser System.out.println("Parsing JSON file"); closeFile(); // EXACT SAME CODE as CsvParser } private void openFile() { System.out.println("Opening file"); } private void closeFile() { System.out.println("Closing file"); } } // openFile() and closeFile() are identical, copy-pasted logic in every parser. // A tenth parser class could forget to call closeFile() and nothing would stop it.
Reason through what's actually fixed and what actually varies. The overall sequence — open, parse, close — is identical for every parser and should never change per subclass; that's the part that belongs in exactly one place. Open and close are literally identical code across every parser, genuinely shared rather than merely similar. Only the middle step, the actual parsing logic, differs per file format, and that's the only piece each specific parser should be responsible for writing.
This is the Template Method pattern: put the overall, fixed sequence in one method on a parent class, locked so subclasses can't accidentally rearrange it, and declare the one genuinely varying step as an abstract method for each subclass to fill in.
Build an abstract DataParser class. openFile() and closeFile() get real, shared implementations, marked protected so they're visible to subclasses but not to outside callers. parseData() is declared protected abstract, with no body at all — every subclass must provide its own. The orchestrating method, parse(), is public final: it calls openFile(), then parseData(), then closeFile(), in that fixed order.
The final on parse() is not a minor detail — it's the entire enforcement mechanism of this pattern. Without it, nothing stops a subclass from overriding parse() itself and skipping closeFile(), silently reintroducing the exact bug this pattern exists to prevent. With it, a subclass can customize exactly one thing — what parseData() does — and nothing else. Each concrete parser then shrinks to a single method: CsvParser extends DataParser and overrides parseData() with its own body, nothing more. Neither concrete class mentions openFile() or closeFile() at all — they're inherited, written once, and guaranteed to run in the right order.
💻 Code example
abstract class DataParser { protected void openFile() { System.out.println("Opening file"); } // shared, written once protected void closeFile() { System.out.println("Closing file"); } protected abstract void parseData(); // no body — every subclass MUST provide its own public final void parse() { // final: the ORDER of these calls can never be overridden openFile(); parseData(); // the one step that changes per subclass closeFile(); } } class CsvParser extends DataParser { @Override protected void parseData() { // the ONLY code this class needs to write System.out.println("Parsing CSV data"); } } class JsonParser extends DataParser { @Override protected void parseData() { System.out.println("Parsing JSON data"); } }
Run both parsers through the same parse() call and confirm the sequence is identical and correct for each: open, format-specific parse, close — every time, for every parser, without either subclass having written that sequence itself.
| Parser | Output sequence |
|---|---|
new CsvParser().parse() | Opening file / Parsing CSV data / Closing file |
new JsonParser().parse() | Opening file / Parsing JSON data / Closing file |
Now prove the actual payoff: add a third format, XML. The entire new class is five lines — extends DataParser, override parseData(), done. openFile() and closeFile() are never rewritten, and structurally cannot be forgotten, because XmlParser never gets the chance to define parse() at all; that method is locked on the parent class. Compare this to the naive version, where a tenth parser class could simply omit closeFile() by accident — here, that specific mistake has become impossible to make, not just unlikely.
This is also a good moment to notice what the pattern deliberately does not let a subclass do: reorder the steps, run parseData() twice, or skip openFile() for some special case. That rigidity is a genuine constraint, not an oversight — if a future format ever needed a real exception to the fixed sequence, that would be a sign this particular format doesn't actually belong under DataParser at all, rather than a reason to weaken the pattern's guarantee for every other parser sharing it.
💻 Code example
class XmlParser extends DataParser { // the entire diff for a new format @Override protected void parseData() { System.out.println("Parsing XML data"); } } public class Main { public static void main(String[] args) { DataParser csvParser = new CsvParser(); csvParser.parse(); // Opening file / Parsing CSV data / Closing file DataParser jsonParser = new JsonParser(); jsonParser.parse(); // Opening file / Parsing JSON data / Closing file DataParser xmlParser = new XmlParser(); xmlParser.parse(); // Opening file / Parsing XML data / Closing file } }
▲ Edge case — forgetting final quietly defeats the entire pattern
Leaving parse() non-final means a subclass can override it and reorder or skip steps the template was specifically designed to guarantee, silently undoing the one thing this pattern exists to enforce. This is the single most common way this pattern gets implemented incorrectly — the final keyword is not decoration, it's the mechanism.
▲ Edge case — optional steps need hooks, not abstract methods
parseData() is mandatory for every subclass here. Sometimes a step is genuinely optional — say, an afterParse() step that only some formats need. The standard fix is a hook: a method with a default, often empty, implementation in the base class, which a subclass can override if it needs to but isn't forced to. Not every step in a template needs to be abstract; some can simply be safely skippable.
▲ Edge case — a failing middle step leaves the sequence half-finished
If parseData() throws an exception, closeFile() — the step right after it — never runs. For a real file parser, that's a resource leak. Production versions typically wrap the body of parse() in a try/finally, so closeFile() is guaranteed to run whether parseData() succeeds or throws.
▲ Trade-off — this pattern couples subclasses to the parent more tightly than most others in this course
Every pattern built so far favors composition specifically to avoid tight coupling between classes. Template Method is a genuine exception: the whole point is a fixed sequence subclasses are not allowed to rearrange, and inheritance, with a final orchestrating method and a protected abstract hook, is precisely the mechanism that enforces "customize this one step, but never touch the overall order." That tighter coupling is the feature here, not an accident — but it does mean a subclass can never opt out of the base class's sequencing, even in a case where that sequencing genuinely doesn't fit.
◆ Where this pattern actually shows up
javax.servlet.http.HttpServlet.service()— defines the fixed sequence of handling an HTTP request and delegates todoGet(),doPost(), and similar hook methods that subclasses override for their specific logic.- JUnit's test lifecycle — a fixed sequence (setup, run the test, tear down) with the specific test logic supplied by each test method, following the identical "framework calls your code" shape.
java.io.InputStream— several of its methods are structured as templates, with a fixed outer method delegating to an abstract or overridable inner read step that concrete subclasses likeFileInputStreamsupply.java.util.AbstractListand related abstract collection classes — provide fixed, shared implementations of higher-level operations (like iteration or equality) built on top of a small number of abstract primitive methods that concrete subclasses must implement.- Any "framework calls your code, not the other way around" design is very often a Template Method underneath — this inversion of control is one of the clearest tells that this pattern is in play.
- Sorting algorithms with a pluggable comparison step — some classic algorithm implementations fix the overall sort strategy while leaving the element comparison itself as the one customizable step, structurally close to a template method even where the language expresses it as a passed-in comparator instead of a subclass hook.
Q: Why is the top-level orchestrating method marked final in a Template Method? : To guarantee subclasses can customize specific steps but can never reorder or skip the overall sequence — that guarantee is the entire point of the pattern.
Q: Why does Template Method deliberately use inheritance instead of composition, unlike most other patterns? : The pattern's whole purpose is enforcing a fixed sequence with specific customizable steps. Inheritance's tighter coupling, via a final orchestrating method and a protected abstract hook, directly enforces that constraint in a way composition can't as cleanly.
Q: What's the difference between an abstract method and a hook in a template method? : An abstract method has no implementation, and every subclass must provide one. A hook has a default, often empty, implementation in the base class, which a subclass may override only if it actually needs to.
Q: What specifically prevents code duplication between parsers in the DataParser example? : openFile() and closeFile() are written once on the abstract DataParser base class and inherited by every subclass — neither concrete parser reimplements them; each only supplies its own parseData().
Q: What real bug can occur if parseData() throws an exception, and how is it typically fixed? : closeFile() never runs, leaking whatever resource it was meant to release. The fix is wrapping the template method's body in a try/finally, so the closing step always runs regardless of whether the middle step succeeds or throws.
Want a visual for this concept?
Generate a diagram tailored to “Template Method Pattern” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →