Template Method
Purpose
Write the algorithm once and let named steps vary. The base class owns the sequence — what happens
in what order, what is invariant, what must always run — and subclasses fill in the parts that
legitimately differ.
The pattern's cost is the strongest coupling Java offers. A subclass depends not only on the base
class's contract but on its self-use: which hooks are called, in what order, with what state
already established, and whether calling super is required. None of that is checked by the
compiler and most of it is undocumented. Prefer composition unless one of the narrow cases below
applies.
Inspect compiler release/toolchains, framework construction paths and external subclasses before
changing hooks. Examples are partial Java 17 sketches with domain types/imports omitted; no
preview or dependency upgrade is required. Missing caller evidence leaves API removal conditional.
When it is the answer
A framework exposes an inherited algorithm with overridable steps
→ retain its required Template Method contract. Inspect injection/registration
options; framework construction alone does not exclude composition.
A test base class specifying a contract every implementation must
satisfy
→ Template Method, and it is clearly right: the subclass
supplies a value and inherits a specification.
A genuinely stable algorithm with cohesive variants
that share substantial state
→ Template Method with documented hooks and an explicit
extension policy. Still compare composition.
When it is not
- One step varies and no inheritance/framework constraint exists. Passing a function is often
cheaper, while a template can still be justified to protect lifecycle or invariants
(
gof-strategy).
- The variants are open without a compatibility policy. Open extension is a legitimate
framework use of Template Method, but hook call order and self-use become published API.
- The hook surface is growing across unrelated concerns. This suggests coordination or
optional-feature pressure; use cohesion and subclass complexity rather than a numeric cutoff.
- Subclasses override the template method itself. Inspect whether this violates a required
invariant or follows a documented extension policy; overriding alone does not settle the issue.
- Steps are contributed by different modules. That is a pipeline or a chain
(
gof-chain-of-responsibility).
Modern Java expression
Classical Composition
─────────────────────────────────── ───────────────────────────────────
abstract class Job { final class Job {
final void run() { private final Steps steps;
var in = read(); void run() {
process(in); var in = steps.read();
write(); steps.process(in);
} steps.write();
protected abstract Input read(); }
protected abstract void process( }
Input in);
} interface Steps { … } — or a record
of function values, or three
class CsvJob extends Job { } parameters to the constructor
The composed version lets steps be tested and reused independently. Multi-method Steps still
needs an implementation; separate functional collaborators can use lambdas. A Steps implementation
can coordinate several operations with private helpers, but call order/lifetime remain contracts.
A middle position that works well: keep the template as a final class with a final method, and
take the steps as constructor parameters. The sequence stays in one place, the variation is
composed, and nothing is inheritable.
Decision rules
IF the template sequence must be invariant
THEN make the template method final. If subclasses may refine the sequence, document
allowed override/super-call behavior and test it as public extension API.
IF the constructor calls a hook
THEN it may read subclass state before initialization. Avoid overridable calls from a
constructor (java-composition-over-inheritance).
IF a hook is protected
THEN it is API for every present and future subclass; changing its
signature, its contract or when it is called is a breaking change.
Keep the surface as small as the algorithm allows.
IF a hook must call super.hook() at a particular point
THEN the base's algorithm has leaked into every subclass and forgetting
the call is a silent bug. Restructure so the base calls two hooks
instead.
IF a subclass overrides a hook to do nothing or throw unsupported
THEN distinguish an intentional optional hook (prefer a documented base no-op) from a
required step the subtype cannot honor, which violates substitutability.
IF the base class holds mutable state between hook calls
THEN define instance confinement/lifetime and what subclasses may observe. A per-run
instance can be safe; a shared instance needs synchronization or, preferably,
a per-run context passed through hooks.
IF only one known variant exists
THEN seek a concrete framework/SPI/lifecycle reason for the hook. Otherwise write the
algorithm directly and extract variation when it becomes real.
IF a step is remote
THEN the template must honor the run's deadline and define partial-run semantics;
transport timeouts may belong to the client and retry classification to an
explicit resilience policy (timeouts-and-deadlines).
Cross-cutting checks
- Concurrency. A template instance shared across threads shares whatever state the base class
keeps between hook calls — a field set by
read() and used by write() is a race, and it is
invisible because each method looks correct alone. Pass a per-run context object through the
hooks to isolate run data; also verify steps, audit/client collaborators and escaping callbacks
before sharing the template instance
(java-memory-model).
- Distribution. Templates commonly wrap batch and ETL runs where a step calls a remote system.
The base class must then own the parts subclasses cannot get right individually: a deadline for
the run, a per-step timeout, a failure classification that decides whether the run retries or
stops, and an explicit answer to "what does a half-finished run leave behind" — a partially
written output, an advanced cursor, an emitted event (
idempotency, retries-and-backoff).
- Performance. Hook dispatch is usually minor but should not be declared free in a measured hot
loop. The cost worth watching is structural: a
template that calls a hook once per record turns a per-record cost into the run's cost, and a
subclass whose hook opens a connection per call converts a batch into N round trips
(
orm-behavioral-patterns).
- Testing. With inheritance, testing the algorithm requires a subclass, and testing a subclass
drags in the base. The invariant sequence
can be tested directly with a purpose-built test subclass. Composition permits independent step
tests. Contract test bases are also useful when their fixture lifecycle and inherited assertions
fit the implementations
(
java-test-design).
Review checklist
Return the invariant sequence, hook contracts/ownership, failure and cleanup paths, proposed
change or reason to retain inheritance, and checks executed versus pending.
References
- Inheritance or composition — the decision table, the
final and hook-design rules, the constructor trap, super-call coupling, the cases where the
hierarchy genuinely wins, and a step-by-step migration to composed steps. Read before adding or
removing a template hierarchy.
- Worked example — a nightly settlement run built as an abstract
base with seven overridable methods, converted to a final template taking composed steps: what the hooks were
hiding, the shared-field race, the remote step's timeout, and how the contract test base class
survived the conversion because it is the case the pattern fits. Read when refactoring.
1---2name: gof-template-method3description: Template Method in modern Java: fixing an algorithm's skeleton while named steps vary, and the inheritance coupling that often makes composition preferable. Covers when final protects the sequence, controlled overriding, minimal hook surfaces, the constructor-calls-an-overridable-method trap, protected hooks becoming an API you cannot change, when the pattern is genuinely right (frameworks that instantiate your subclass, contract test base classes), and how to convert one to a class taking its steps as collaborators. Use when an abstract base class with protected hooks is proposed, when a base-class change broke subclasses, when a template has grown past a handful of hooks, or when subclasses override the template method itself. Does not cover choosing a whole algorithm (gof-strategy), creating the product a template needs (gof-factory-method), the general inheritance decision (java-composition-over-inheritance), or pipeline stages contributed independently (gof-chain-of-responsibility).4---56# Template Method78## Purpose910Write the algorithm once and let named steps vary. The base class owns the sequence — what happens11in what order, what is invariant, what must always run — and subclasses fill in the parts that12legitimately differ.1314The pattern's cost is the strongest coupling Java offers. A subclass depends not only on the base15class's contract but on its self-use: which hooks are called, in what order, with what state16already established, and whether calling `super` is required. None of that is checked by the17compiler and most of it is undocumented. Prefer composition unless one of the narrow cases below18applies.1920Inspect compiler release/toolchains, framework construction paths and external subclasses before21changing hooks. Examples are partial Java 17 sketches with domain types/imports omitted; no22preview or dependency upgrade is required. Missing caller evidence leaves API removal conditional.2324## When it is the answer2526```text27A framework exposes an inherited algorithm with overridable steps28 → retain its required Template Method contract. Inspect injection/registration29 options; framework construction alone does not exclude composition.3031A test base class specifying a contract every implementation must32satisfy33 → Template Method, and it is clearly right: the subclass34 supplies a value and inherits a specification.3536A genuinely stable algorithm with cohesive variants37that share substantial state38 → Template Method with documented hooks and an explicit39 extension policy. Still compare composition.40```4142## When it is not4344- **One step varies and no inheritance/framework constraint exists.** Passing a function is often45 cheaper, while a template can still be justified to protect lifecycle or invariants46 (`gof-strategy`).47- **The variants are open without a compatibility policy.** Open extension is a legitimate48 framework use of Template Method, but hook call order and self-use become published API.49- **The hook surface is growing across unrelated concerns.** This suggests coordination or50 optional-feature pressure; use cohesion and subclass complexity rather than a numeric cutoff.51- **Subclasses override the template method itself.** Inspect whether this violates a required52 invariant or follows a documented extension policy; overriding alone does not settle the issue.53- **Steps are contributed by different modules.** That is a pipeline or a chain54 (`gof-chain-of-responsibility`).5556## Modern Java expression5758```text59Classical Composition60─────────────────────────────────── ───────────────────────────────────61abstract class Job { final class Job {62 final void run() { private final Steps steps;63 var in = read(); void run() {64 process(in); var in = steps.read();65 write(); steps.process(in);66 } steps.write();67 protected abstract Input read(); }68 protected abstract void process( }69 Input in);70} interface Steps { … } — or a record71 of function values, or three72class CsvJob extends Job { } parameters to the constructor73```7475The composed version lets steps be tested and reused independently. Multi-method Steps still76needs an implementation; separate functional collaborators can use lambdas. A Steps implementation77can coordinate several operations with private helpers, but call order/lifetime remain contracts.7879A middle position that works well: keep the template as a `final` class with a `final` method, and80take the steps as constructor parameters. The sequence stays in one place, the variation is81composed, and nothing is inheritable.8283## Decision rules8485```text86IF the template sequence must be invariant87THEN make the template method final. If subclasses may refine the sequence, document88 allowed override/super-call behavior and test it as public extension API.8990IF the constructor calls a hook91THEN it may read subclass state before initialization. Avoid overridable calls from a92 constructor (java-composition-over-inheritance).9394IF a hook is protected95THEN it is API for every present and future subclass; changing its96 signature, its contract or when it is called is a breaking change.97 Keep the surface as small as the algorithm allows.9899IF a hook must call super.hook() at a particular point100THEN the base's algorithm has leaked into every subclass and forgetting101 the call is a silent bug. Restructure so the base calls two hooks102 instead.103104IF a subclass overrides a hook to do nothing or throw unsupported105THEN distinguish an intentional optional hook (prefer a documented base no-op) from a106 required step the subtype cannot honor, which violates substitutability.107108IF the base class holds mutable state between hook calls109THEN define instance confinement/lifetime and what subclasses may observe. A per-run110 instance can be safe; a shared instance needs synchronization or, preferably,111 a per-run context passed through hooks.112113IF only one known variant exists114THEN seek a concrete framework/SPI/lifecycle reason for the hook. Otherwise write the115 algorithm directly and extract variation when it becomes real.116117IF a step is remote118THEN the template must honor the run's deadline and define partial-run semantics;119 transport timeouts may belong to the client and retry classification to an120 explicit resilience policy (timeouts-and-deadlines).121```122123## Cross-cutting checks124125- **Concurrency.** A template instance shared across threads shares whatever state the base class126 keeps between hook calls — a field set by `read()` and used by `write()` is a race, and it is127 invisible because each method looks correct alone. Pass a per-run context object through the128 hooks to isolate run data; also verify steps, audit/client collaborators and escaping callbacks129 before sharing the template instance130 (`java-memory-model`).131- **Distribution.** Templates commonly wrap batch and ETL runs where a step calls a remote system.132 The base class must then own the parts subclasses cannot get right individually: a deadline for133 the run, a per-step timeout, a failure classification that decides whether the run retries or134 stops, and an explicit answer to "what does a half-finished run leave behind" — a partially135 written output, an advanced cursor, an emitted event (`idempotency`, `retries-and-backoff`).136- **Performance.** Hook dispatch is usually minor but should not be declared free in a measured hot137 loop. The cost worth watching is structural: a138 template that calls a hook once per record turns a per-record cost into the run's cost, and a139 subclass whose hook opens a connection per call converts a batch into N round trips140 (`orm-behavioral-patterns`).141- **Testing.** With inheritance, testing the algorithm requires a subclass, and testing a subclass142 drags in the base. The invariant sequence143 can be tested directly with a purpose-built test subclass. Composition permits independent step144 tests. Contract test bases are also useful when their fixture lifecycle and inherited assertions145 fit the implementations146 (`java-test-design`).147148## Review checklist149150Return the invariant sequence, hook contracts/ownership, failure and cleanup paths, proposed151change or reason to retain inheritance, and checks executed versus pending.152153- [ ] The template method is final when sequence invariance is required; otherwise override policy is explicit154- [ ] No constructor calls an overridable hook155- [ ] The hook surface is small, documented, and does not require `super` calls at set points156- [ ] Optional no-op hooks are explicit; required hooks preserve substitutability157- [ ] Mutable cross-hook state is confined, synchronized, or carried in a per-run context158- [ ] Multiple variants or a concrete framework/SPI extension constraint exists159- [ ] Remote deadline, transport timeout and resilience ownership are explicit160- [ ] A partial run's effects are defined161- [ ] Composition was considered, and the reason for inheritance is stated162163## References164165- [Inheritance or composition](references/inheritance-or-composition.md) — the decision table, the166 `final` and hook-design rules, the constructor trap, `super`-call coupling, the cases where the167 hierarchy genuinely wins, and a step-by-step migration to composed steps. Read before adding or168 removing a template hierarchy.169- [Worked example](references/worked-example.md) — a nightly settlement run built as an abstract170 base with seven overridable methods, converted to a final template taking composed steps: what the hooks were171 hiding, the shared-field race, the remote step's timeout, and how the contract test base class172 survived the conversion because it is the case the pattern fits. Read when refactoring.