Defining an algorithm's fixed skeleton in a base class while deferring individual steps to subclasses — via a read-validate-transform-save DataProcessor.
Published September 23, 2026
Some algorithms have a sequence of steps that should always happen in the same order, but where individual steps vary by context. Template Method puts the fixed sequence in a base class method (the "template"), marked final so subclasses can't reorder it, while individual steps are abstract (or have a default, overridable implementation) for subclasses to customize.
abstract class DataProcessor {
// the template — fixed sequence, cannot be reordered or skipped by subclasses
public final void process() {
List<String> raw = read();
List<String> valid = validate(raw);
List<String> transformed = transform(valid);
save(transformed);
}
protected abstract List<String> read();
protected abstract List<String> validate(List<String> raw);
protected abstract List<String> transform(List<String> valid);
protected abstract void save(List<String> data);
}
class CsvDataProcessor extends DataProcessor {
protected List<String> read() { return readCsvFile(); }
protected List<String> validate(List<String> raw) { return raw.stream().filter(this::isValidRow).toList(); }
protected List<String> transform(List<String> valid) { return valid.stream().map(this::normalize).toList(); }
protected void save(List<String> data) { writeToDatabase(data); }
// helper methods omitted
}
class JsonDataProcessor extends DataProcessor {
protected List<String> read() { return readJsonFile(); }
protected List<String> validate(List<String> raw) { return raw.stream().filter(this::isValidJson).toList(); }
protected List<String> transform(List<String> valid) { return valid.stream().map(this::flatten).toList(); }
protected void save(List<String> data) { writeToBlobStorage(data); }
}
DataProcessor csv = new CsvDataProcessor();
csv.process(); // always read -> validate -> transform -> save, in that exact order, guaranteed
Every subclass gets the same guaranteed sequence — you can't accidentally call save() before validate() from within a subclass, because subclasses never call process()'s steps directly; they only ever implement individual steps that the (unoverridable, final) template calls in the fixed order.
Both let you vary behavior, but through opposite mechanisms: Template Method customizes individual steps of an algorithm via inheritance (a subclass overrides specific abstract methods, but the overall structure lives in the base class and can't be swapped as a whole). Strategy swaps the entire algorithm via composition (a completely different PricingStrategy object can be substituted, with no shared base-class structure constraining it at all). Template Method fits when the steps' order must stay fixed and only how each step works varies; Strategy fits when the whole algorithm might be replaced wholesale, with no assumption that it shares any structure with the alternative.
The core idea is simple: a base class fixes an algorithm's skeleton, and subclasses fill in steps. What makes it valuable in real code is that the base class can guarantee things every variant must do, such as validation, timing, cleanup and error handling, so individual subclasses can't forget them.
public abstract class ReportGenerator {
/** The template method: final, so the sequence and its guarantees can't be bypassed. */
public final Report generate(ReportRequest request) {
validate(request); // invariant step, same for all reports
Instant start = Instant.now();
try (DataCursor data = openData(request)) { // resource ALWAYS closed
List<Row> rows = new ArrayList<>();
while (data.hasNext()) {
Row row = data.next();
if (include(row)) rows.add(transform(row)); // hook + abstract step
}
Report report = render(request, rows); // abstract step
afterRender(report); // optional hook
return report;
} catch (IOException e) {
throw new ReportFailedException(getClass().getSimpleName(), e); // uniform error type
} finally {
metrics.record(getClass().getSimpleName(), Duration.between(start, Instant.now()));
}
}
// --- steps subclasses MUST provide ---
protected abstract DataCursor openData(ReportRequest request) throws IOException;
protected abstract Report render(ReportRequest request, List<Row> rows);
// --- hooks subclasses MAY override (sensible defaults) ---
protected boolean include(Row row) { return true; }
protected Row transform(Row row) { return row; }
protected void afterRender(Report report) {}
// --- private: not overridable at all ---
private void validate(ReportRequest r) {
if (r.from().isAfter(r.to())) throw new IllegalArgumentException("from must be before to");
}
}
public class MonthlySalesReport extends ReportGenerator {
@Override protected DataCursor openData(ReportRequest r) { return salesRepo.cursor(r.from(), r.to()); }
@Override protected boolean include(Row row) { return !row.getBoolean("test_order"); } // hook used
@Override protected Report render(ReportRequest r, List<Row> rows) { return new CsvReport(rows); }
}
Three kinds of methods, and each choice is deliberate:
| Kind | Modifier | Purpose |
|---|---|---|
| Template method | public final | The fixed sequence. Can't be overridden, so its guarantees hold |
| Abstract steps | protected abstract | The parts that genuinely differ. The compiler forces subclasses to supply them |
| Hooks | protected with a default | Optional customization points. Most subclasses ignore them |
| Invariant helpers | private | Steps no subclass should change |
The guarantees are the point. Every report is validated, closes its data cursor even on failure, records timing metrics, and reports errors the same way, without any subclass having to remember to.
JdbcTemplate, RestTemplate and TransactionTemplate: the "template" in their names is this idea, though implemented with callbacks instead of inheritance. JdbcTemplate handles getting a connection, creating the statement, translating exceptions and closing resources. You supply only the SQL and a RowMapper.AbstractList, AbstractMap in the JDK: implement get and size, and you inherit dozens of methods built on top of them.@BeforeEach → test → @AfterEach is a fixed sequence with hooks.HttpServlet.service() dispatches to doGet/doPost, which you override.Inheritance ties each variant to one base class, and Java allows only one. The same skeleton can be written with functions passed in (composition), which is why modern APIs often prefer it:
public <T> T inTransaction(Function<Connection, T> work) {
try (Connection c = dataSource.getConnection()) {
c.setAutoCommit(false);
try { T result = work.apply(c); c.commit(); return result; }
catch (RuntimeException e) { c.rollback(); throw e; }
} catch (SQLException e) { throw new DataAccessException(e); }
}
Order saved = inTransaction(conn -> orderDao.insert(conn, order));
Use a Template Method class hierarchy when there are several steps and hooks, and a natural "is-a" relationship. Use callbacks when there are one or two varying steps, or when the variants shouldn't have to extend a particular base class.
Q: Why mark the template method final?
A: To enforce the entire point of the pattern — if subclasses could override process() itself, they could reorder or skip steps, which defeats the guarantee that the sequence is fixed. final makes that guarantee a compile-time fact, not a convention subclasses are trusted to follow.
Q: Can a step have a default implementation instead of being purely abstract? A: Yes — a "hook" method with a default (often no-op) implementation lets subclasses optionally override just the steps they care about, while inheriting sensible defaults for the rest. This is common when most steps are usually the same across subclasses and only one or two genuinely vary.
Q: Isn't this just normal inheritance and method overriding — what makes it a distinct 'pattern'?
A: The pattern-worthy part isn't overriding itself, it's the specific structure: a final (or otherwise protected-from-override) method that calls several abstract/hook methods in a fixed sequence. That structural commitment — 'the algorithm's shape is fixed, only its steps vary' — is what distinguishes Template Method from arbitrary polymorphism.
Q: How would you test a subclass's individual step in isolation, without running the whole template sequence?
A: Since each step is its own protected method, you can test it directly if you expose it at package-visibility or via a test subclass — though testing through process() end-to-end is usually more valuable, since the steps are specifically meant to be evaluated as part of the fixed sequence, not independently.
Q: What's a "hook" in Template Method, and how is it different from an abstract step? A: An abstract step must be implemented by every subclass, because the algorithm can't work without it. A hook has a default implementation (often doing nothing), and subclasses override it only when they need to customize that point.
Q: How is Spring's JdbcTemplate related to the Template Method pattern?
A: It applies the same idea (a fixed skeleton of acquiring a connection, executing, mapping, translating exceptions and releasing resources) but supplies the varying parts through callbacks such as RowMapper, rather than through subclassing. That's the composition-based variant of Template Method.
Q: What's the main risk of Template Method? A: Fragile base classes. Subclasses depend on the exact order and behaviour of the skeleton, so changes to the base class can break them in subtle ways. Keep the template small, document the contract of each step, and prefer callbacks when variants are few.