Template Method Pattern
The skeleton of an algorithm in the base class; specifics in the subclass hooks. When inheritance is the right answer.
What it is#
The Template Method pattern defines the skeleton of an algorithm in a base class and lets subclasses fill in specific steps without changing the algorithm’s overall shape. The Gang of Four called it the embodiment of the Hollywood Principle: “don’t call us, we’ll call you.” The base class is in charge; the subclass provides the parts the base class asks for.
Concretely: the base class has one public final method — the template method — that calls a sequence of protected methods. Some of those methods have default implementations; some are abstract and must be overridden; some are hooks with empty defaults that subclasses optionally fill in. The order of steps, the invariants, and the cross-cutting concerns (logging, locking, retries) live in the base. The variable steps live in the subclass.
This is one of the rare patterns where inheritance is the right answer. Most modern design advice favors composition; Template Method is the case where the algorithm genuinely is a single thing with holes in it, and pulling the holes out into a separate object adds machinery without buying flexibility.
Class structure#
┌───────────────────────────────┐ │ AbstractAlgorithm │ ├───────────────────────────────┤ │ + final templateMethod() │ ← the skeleton; do not override │ # step1() │ ← abstract: subclass must define │ # step2() │ ← concrete default; can override │ # hookA() │ ← empty hook; optional override │ # hookB() │ ← empty hook; optional override └──────────────┬────────────────┘ │ ┌────────┴────────┐ │ │ ┌──────────┴─────┐ ┌─────────┴────────┐ │ ConcreteAlgoX │ │ ConcreteAlgoY │ ├────────────────┤ ├──────────────────┤ │ # step1() │ │ # step1() │ │ # hookA() │ │ # step2() │ └────────────────┘ └──────────────────┘final on the template method is load-bearing — subclasses must not be able to re-order or skip steps.
When to use it#
Reach for Template Method when every one of these holds:
- There is a fixed algorithm shape — a definite sequence of steps that does not vary.
- A subset of the steps varies by case — each subclass fills in the same holes differently.
- The steps share invariants and cross-cuts (logging, locking, transaction boundaries, error handling) that you do not want to duplicate.
- The variation is finite and known — a small number of subclasses that exist in the codebase, not a runtime-pluggable catalog.
Common scenarios:
- HTTP request lifecycle — parse → authenticate → authorize → handle → serialize → log. Every handler shares the skeleton; only
handlevaries. - Build pipelines — prepare → compile → test → package → publish. Each language’s build defines its own
compile. - Data processing jobs — open source → transform rows → write sink → close. Each job overrides
transform. - Game loops — handle input → update physics → render → present. Each scene overrides what it does between the fixed phases.
- Algorithm families with the same shape — recursive descent parsers, tree-walking interpreters, two-phase commit coordinators.
When not to use it:
- When the variation point is chosen at runtime. Template Method binds the variant at compile time (via the subclass); if the user picks the algorithm from a config file,
Strategyis the better tool. - When there is no real shared skeleton. Two methods that share four lines of boilerplate do not need a base class — extract a helper and move on.
- When the variation explodes into flag-driven hooks — a base class with twelve overridable hooks is a sign the algorithm itself is not stable and should be broken apart.
How it works#
A canonical example: a batch data importer that follows the same skeleton — open source, read records, validate, transform, write to sink, close — but varies by source format (CSV, JSON, XML).
import java.util.List;import java.util.ArrayList;
public abstract class BatchImporter { // The template method. Final so subclasses cannot reorder or skip steps. public final ImportReport run(String source) { long start = System.currentTimeMillis(); beforeOpen(source); open(source); try { List<Record> records = readAll(); List<Record> valid = new ArrayList<>(); int rejected = 0; for (Record r : records) { if (isValid(r)) valid.add(transform(r)); else rejected++; } writeAll(valid); return new ImportReport(records.size(), valid.size(), rejected, System.currentTimeMillis() - start); } finally { close(); afterClose(); } }
// Abstract: every subclass MUST define these. protected abstract void open(String source); protected abstract List<Record> readAll(); protected abstract void close();
// Concrete defaults: override only when behavior differs. protected Record transform(Record r) { return r; // pass-through by default }
protected boolean isValid(Record r) { return r != null && r.id() != null; }
// Empty hooks: optional override points. protected void beforeOpen(String source) { /* default: no-op */ } protected void afterClose() { /* default: no-op */ }}
public final class CsvImporter extends BatchImporter { private java.io.BufferedReader reader;
@Override protected void open(String source) { try { reader = java.nio.file.Files.newBufferedReader(java.nio.file.Path.of(source)); } catch (java.io.IOException e) { throw new RuntimeException("cannot open " + source, e); } }
@Override protected List<Record> readAll() { try { List<Record> out = new ArrayList<>(); String line; reader.readLine(); // skip header while ((line = reader.readLine()) != null) { String[] cols = line.split(","); out.add(new Record(cols[0], cols[1])); } return out; } catch (java.io.IOException e) { throw new RuntimeException("read failure", e); } }
@Override protected void close() { try { if (reader != null) reader.close(); } catch (java.io.IOException ignored) {} }
// Override the hook to add format-specific validation. @Override protected boolean isValid(Record r) { return super.isValid(r) && !r.name().isBlank(); }
// Subclass writes to a sink defined elsewhere. protected void writeAll(List<Record> rs) { /* sink call omitted */ }}
public record Record(String id, String name) {}public record ImportReport(int read, int written, int rejected, long durationMs) {}Five details earn their keep:
runisfinal. Subclasses can change what each step does but not the order or presence of steps. That guarantee is the whole point.- The hooks are
protected, notpublic. Only subclasses see them. Callers see onlyrun. try/finallyaround the body. The base class owns the cleanup invariant —closeruns whetherreadAllsucceeded or threw. The subclass cannot get this wrong because the subclass does not write the lifecycle.- Defaulted hooks for optional steps.
beforeOpenandafterCloseare empty in the base. Subclasses override when needed; most do not. transformandisValidhave sensible defaults. Subclasses override only what differs from the default — the skeleton encodes a useful zero-config behavior.
The Hollywood Principle in three words: BatchImporter.run is in charge. CsvImporter provides what it is asked for and nothing more.
Variants#
| Variant | Mechanism | When it fits |
|---|---|---|
| Pure-abstract skeleton | Every step is abstract; subclasses define all of them. | The shape is shared but no step has a meaningful default. Rare; usually signals a thin abstraction. |
| Mostly-default skeleton | Most steps have defaults; subclasses override only what differs. | The most common shape — JDK servlets, Spring template classes. The base does the boring 80%. |
| Hook-rich skeleton | Many empty hooks that subclasses optionally fill (beforeX, afterY). | Lifecycle frameworks (servlets, JUnit, Android Activity). The framework calls; the user fills. |
| Functional Template Method | The “subclass” is a set of lambdas passed to the template. | When inheritance would be ceremony — a single function that takes function parameters for the variable steps. The line between this and Strategy blurs. |
The hook-rich variant is what makes a framework feel like a framework. HttpServlet.service() dispatches to doGet, doPost, doPut, doDelete. JUnit TestCase has setUp, tearDown. Android’s Activity has onCreate, onResume, onPause. The user never calls service or tearDown or onResume — the framework does, at the right moments.
Example systems#
The pattern is the spine of half the JDK:
javax.servlet.http.HttpServlet—service(req, resp)is the template;doGet,doPost,doPut,doDeleteare the hooks. The book example. The Servlet API has used this since 1997.java.util.AbstractList,AbstractMap,AbstractSet— the template methods areiterator,size,equals,hashCode,toString. Subclasses (ArrayList,LinkedList) implement the few primitive operations; the rest of theListcontract falls out for free.AbstractQueuedSynchronizer— the basis forReentrantLock,Semaphore,CountDownLatch. AQS defines the acquire/release skeleton; subclasses definetryAcquireandtryRelease.InputStream.read(byte[], int, int)— has a default implementation in terms of single-byteread(). Subclasses can override either depending on which is more efficient.- JUnit 3-style
TestCase—runTestis the template;setUp,tearDown, and the test method itself are the hooks. - Spring’s
JdbcTemplate,RestTemplate,JmsTemplate— the family is literally named after the pattern. Each template owns resource acquisition, error translation, and cleanup; the caller supplies the variable callback.
Trade-offs#
What you gain:
- The skeleton is written once. Resource lifecycle, error handling, logging, transaction boundaries — implemented in the base, free for every subclass.
- Invariants are enforced.
finalon the template means subclasses cannot break the order or skip a step. - Subclasses are small. A new importer is two abstract methods, not a whole pipeline.
- Hollywood Principle in action. Frameworks lift the boring control flow out of user code. The user writes what is specific; the framework handles when it runs.
What you pay:
- Inheritance is rigid. A subclass is bound to one base. If
CsvImporteralso wants to be aSchedulable, multiple inheritance (which Java does not have) or refactoring to composition is the cost. - The Liskov Substitution Principle is at risk. A subclass that violates the base’s invariants (e.g.,
transformreturnsnullwhen the base expects non-null) produces bugs the base author did not anticipate. Document hook contracts. - Reordering steps is a breaking change. Once subclasses rely on
step2running afterstep1, moving them around in the template silently breaks every subclass. - Hook proliferation is a smell. Eight hooks in one base class usually means the algorithm itself is unstable or that two different algorithms have been forced into one base. Split the base.
- Testing requires concrete subclasses. You cannot instantiate the abstract base; tests need a minimal real subclass or a mock. JDK templates typically ship test fixtures that double as documentation.
Template Method: inheritance
The base owns the algorithm; subclasses fill in steps. Selection of which “variant” is implicit in the type (CsvImporter vs JsonImporter). Binding is compile-time.
Best when: a real algorithm skeleton exists; the variation is by kind of thing; cross-cutting concerns live in the base.
Strategy: composition
The context holds a strategy reference and calls it. Selection is runtime (setStrategy(...)). The context owns no algorithm — just the strategy slot.
Best when: variants are runtime-pluggable; there is no shared skeleton; the algorithm is the variable thing.
The patterns are not rivals — they answer different questions. Template Method asks “where do I put the variable steps of a fixed algorithm?” Strategy asks “where do I put a whole algorithm that has alternatives?” A complex system uses both: the request-handling template calls a pricing strategy.
Related patterns#
- Strategy Pattern — the composition-based cousin. Use Strategy for runtime-pluggable algorithms; use Template Method for compile-time variants with a real skeleton.
- Factory Method Pattern — Factory Method is literally a Template Method whose hook is “which object do I create?” The original GoF chapter introduces them together.
- Open Closed Principle (OCP) — Template Method is OCP for “the algorithm shape is stable, but the steps need new variants.” The base is closed; the subclass extends.
- Liskov Substitution Principle (LSP) — every Template Method base class is selling an LSP contract: any subclass must honor the documented step semantics. Violations are subtle and ugly.
- OOP Fundamentals — The Four Pillars, Polymorphism — Template Method is what people mean when they say inheritance is for “is-a” relationships that share behavior. It is the pattern where inheritance pays its rent.
- Observer Pattern, Command Pattern — both compose naturally with Template Method: a
JmsTemplate(template) can fire anOnMessageevent (observer) carrying a command for the listener to execute later.