Template Method Pattern

The skeleton of an algorithm in the base class; specifics in the subclass hooks. When inheritance is the right answer.

Pattern Foundational
10 min read
pattern behavioral template-method inheritance hooks

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 handle varies.
  • 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, Strategy is 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:

  • run is final. 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, not public. Only subclasses see them. Callers see only run.
  • try/finally around the body. The base class owns the cleanup invariant — close runs whether readAll succeeded or threw. The subclass cannot get this wrong because the subclass does not write the lifecycle.
  • Defaulted hooks for optional steps. beforeOpen and afterClose are empty in the base. Subclasses override when needed; most do not.
  • transform and isValid have 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#

VariantMechanismWhen it fits
Pure-abstract skeletonEvery 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 skeletonMost 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 skeletonMany empty hooks that subclasses optionally fill (beforeX, afterY).Lifecycle frameworks (servlets, JUnit, Android Activity). The framework calls; the user fills.
Functional Template MethodThe “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.HttpServletservice(req, resp) is the template; doGet, doPost, doPut, doDelete are the hooks. The book example. The Servlet API has used this since 1997.
  • java.util.AbstractList, AbstractMap, AbstractSet — the template methods are iterator, size, equals, hashCode, toString. Subclasses (ArrayList, LinkedList) implement the few primitive operations; the rest of the List contract falls out for free.
  • AbstractQueuedSynchronizer — the basis for ReentrantLock, Semaphore, CountDownLatch. AQS defines the acquire/release skeleton; subclasses define tryAcquire and tryRelease.
  • InputStream.read(byte[], int, int) — has a default implementation in terms of single-byte read(). Subclasses can override either depending on which is more efficient.
  • JUnit 3-style TestCaserunTest is 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. final on 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 CsvImporter also wants to be a Schedulable, 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., transform returns null when 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 step2 running after step1, 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.

  • 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 an OnMessage event (observer) carrying a command for the listener to execute later.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.