Three reports in a sales system: a CSV export, an HTML export, and a CSV export for orders of 1,000,000 VND or more. All three open a connection, filter, print a header, loop over rows, print a footer, and close. Only a few steps differ. If each report is its own class with a copy of the whole procedure, a change like "log before exporting" means editing three places. The Template Method pattern solves exactly this: the parent class owns the algorithm skeleton, and subclasses fill in only the steps that differ. This article builds a complete example, shows three common design mistakes, finds Template Method in the JDK (InputStream, AbstractList) and Spring (JdbcTemplate), and compares it with Strategy. The code runs on JDK 21 (language level 17), and the output is pasted verbatim.

The previous article, Observer pattern: from listeners to event buses and the Flow API, attached behavior from the outside. Template Method does the opposite: the skeleton is fixed on the inside, and the extension points are declared up front.

What Template Method is and its parts

According to refactoring.guru, Template Method defines the skeleton of an algorithm in a parent class and lets subclasses override specific steps without changing the overall structure. The GoF book places it among the behavioral patterns, built on inheritance. The parts:

PartRoleIn the example
Template methodCalls the steps in a fixed order; should be declared finalexport()
Abstract stepSubclasses must implement itheader(), row()
Step with a defaultSubclasses may override itfilter(), footer()
HookAn empty step used as an insertion point; the algorithm runs normally if it is not overriddenbeforeExport(), afterExport()

What sets Template Method apart from ordinary inheritance is inversion of control: the subclass does not call the parent, the parent calls the subclass. This is often called the "Hollywood principle": don't call us, we'll call you.

Implementing Template Method step by step

The first step is to write the skeleton. The abstract class OrderExporter has an export() method declared final, so subclasses cannot break the order. The log list exists only to verify the order of steps in the demo:

static abstract class OrderExporter {
    protected final List<String> log = new ArrayList<>();

    public final String export(List<Order> orders) {          // template method
        StringBuilder sb = new StringBuilder();
        log.add("begin");
        beforeExport(sb);                                      // hook
        log.add("header");
        sb.append(header());                                   // required step
        for (Order o : filter(orders)) {                       // step with a default
            sb.append(row(o));                                 // required step
        }
        log.add("footer");
        sb.append(footer(orders.size()));                      // step with a default
        afterExport(sb);                                       // hook
        return sb.toString();
    }

    protected abstract String header();
    protected abstract String row(Order o);

    protected List<Order> filter(List<Order> orders) { return orders; }
    protected String footer(int total) { return ""; }
    protected void beforeExport(StringBuilder sb) {}
    protected void afterExport(StringBuilder sb) {}
}

Subclasses write only what differs. CsvExporter implements the two required steps. HtmlExporter also uses two hooks to wrap the output in a <table> tag and overrides footer. BigOrderCsvExporter extends CsvExporter and overrides only filter:

static class CsvExporter extends OrderExporter {
    @Override protected String header() { return "id,customer,amount\n"; }
    @Override protected String row(Order o) { return o.id() + "," + o.customer() + "," + o.amount() + "\n"; }
}

static class BigOrderCsvExporter extends CsvExporter {
    @Override protected List<Order> filter(List<Order> orders) {
        return orders.stream().filter(o -> o.amount() >= 1_000_000).toList();
    }
}

Running with three sample orders (ReportDemo.java):

== CsvExporter
id,customer,amount
ORD-1,An,450000
ORD-2,Binh,2300000
ORD-3,Chi,1000000
   step order: [begin, header, footer]
== HtmlExporter
<table>
<tr><th>ID</th><th>Customer</th><th>Amount</th></tr>
<tr><td>ORD-1</td><td>An</td><td>450000</td></tr>
<tr><td>ORD-2</td><td>Binh</td><td>2300000</td></tr>
<tr><td>ORD-3</td><td>Chi</td><td>1000000</td></tr>
<tr><td colspan=3>Total: 3 orders</td></tr>
</table>
   step order: [begin, header, footer]
== BigOrderCsvExporter
id,customer,amount
ORD-2,Binh,2300000
ORD-3,Chi,1000000
   step order: [begin, header, footer]

The three outputs differ, but the order of steps is identical. One detail deserves a close read: HtmlExporter prints "Total: 3 orders" based on orders.size(), which is the number of orders before filtering. If a subclass filters out some orders, the footer reports the wrong count. This shows that the skeleton and its steps must agree on the data passed between them: the footer(int total) parameter should be the number of rows actually printed, not the number of inputs. When you write a skeleton, ask what each step receives and what it promises to return. The sample code deliberately keeps this weakness for illustration; BigOrderCsvExporter does not expose the bug because the CSV footer is empty.

Three common design mistakes with Template Method

Template Method uses inheritance, so it carries every risk of inheritance. The program Pitfalls.java reproduces three mistakes, each with real output.

Mistake 1: calling an overridable step from the parent constructor

static abstract class Report {
    Report() { System.out.println("  Report(): title = " + title()); }
    abstract String title();
}
static class SalesReport extends Report {
    private final String name;
    SalesReport(String name) { this.name = name; }
    @Override String title() { return name; }
}
--- 1. Constructor calls an overridable step ---
  Report(): title = null

The parent constructor runs before the subclass fields are assigned, so title() returns null even though the field is declared final. This is a warning from Joshua Bloch's book Effective Java: a constructor must not call methods that can be overridden. In Template Method, do not call the template method (or any step) from a constructor; split it into a run() or init() method that is called after the object is fully built.

Mistake 2: a subclass skips a default step and breaks the contract

static abstract class Notifier {
    final void send(String msg) {
        String text = format(msg);
        System.out.println("  sending: \"" + text + "\"  (length " + text.length() + ")");
    }
    String format(String msg) { return msg.strip(); }          // default step
}
static class SloppyNotifier extends Notifier {
    @Override String format(String msg) { return msg; }       // forgot strip()
}
--- 2. A default step is skipped ---
  sending: "hello"  (length 5)
  sending: "  hello  "  (length 9)

The parent class Notifier calls strip() by default in the format step. SloppyNotifier overrides it and forgets that line, so the whitespace gets through. Refactoring.guru warns that this is a weakness: overriding a default step in a way that drops the original behavior can violate the Liskov substitution principle, because the subclass no longer behaves like its parent. The remedy is to document each step's contract in Javadoc (inputs, outputs, and what must be preserved), or, if the default behavior is mandatory, to move it into the template method instead of leaving it in an overridable step.

Mistake 3: forgetting to declare the template method final

The code below is missing final on run() (the skeleton method), so a subclass can override the whole thing:

static class Loose {
    void run() { System.out.println("  Loose.run: steps 1, 2, 3"); }   // missing final
}
static class Hijack extends Loose {
    @Override void run() { System.out.println("  Hijack.run: skips the skeleton, does only step 2"); }
}
// Loose l = new Hijack(); l.run();
--- 3. Template method is not final ---
  Hijack.run: skips the skeleton, does only step 2

Without final, the subclass overrides the skeleton method itself and every promise about ordering disappears. This is the easiest mistake to avoid: make the template method public final (or package-private final if it is used only within the package), and make the steps protected, not public, so outsiders cannot call individual steps.

Template Method in the JDK: InputStream and AbstractList

The JDK uses Template Method heavily, and the two examples below are easy to verify. According to the InputStream Javadoc, the only abstract method is read(). Per the Javadoc, read(byte[], int, int) is defined by repeatedly calling read() (other methods such as skip, readAllBytes and available have their own behavior), and the Javadoc encourages subclasses to override it for efficiency. AbstractList is similar: get(int) and size() alone give you a complete read-only list; the modifying operations (set, add, remove) are optional steps that throw UnsupportedOperationException by default. The JdkDemo.java example:

static class CountingStream extends InputStream {
    private int next = 0;
    private final int limit;
    int readCalls = 0;
    CountingStream(int limit) { this.limit = limit; }
    @Override public int read() {
        readCalls++;
        return next < limit ? 'a' + (next++ % 26) : -1;
    }
}

static class Range extends AbstractList<Integer> {
    private final int from, to;
    Range(int from, int to) { this.from = from; this.to = to; }
    @Override public Integer get(int i) { return from + i; }
    @Override public int size() { return to - from; }
}
read(byte[]) read 10 bytes: abcdefghij, read() calls = 11
readAllBytes -> abcde, read() calls = 7
Range: [10, 11, 12, 13, 14], contains(12) = true, indexOf(14) = 4, subList(1,3) = [11, 12]
add() -> UnsupportedOperationException (optional step not overridden)

The 11 on the first line is 10 bytes plus one read() that returns -1 to signal the end of the stream. The second line shows 7 calls for only 5 bytes of data: readAllBytes() is also built on read(byte[], int, int), and it needs one more round to confirm the stream has ended. We implement nothing except read(), yet we get all the block-reading methods, which matches the spirit of Template Method: the parent owns the algorithm, and the subclass supplies the raw material. The trade-off is that the default reads one byte at a time, so it is slow. That is why the Javadoc encourages overriding read(byte[], int, int) when you can read a whole block from the source.

Template Method in Spring: JdbcTemplate and callbacks

Spring uses a variant: instead of inheritance, the skeleton receives behavior through callbacks. According to the JdbcTemplate documentation, this class handles all the repetitive parts of JDBC (getting a connection, creating a statement, executing it, iterating over results, releasing resources, translating exceptions), while application code supplies only the SQL, the parameters, and how to handle results through callbacks such as RowMapper. To make the structure visible, the JdbcDemo.java example writes a MiniJdbc of just a few lines and compares it with the real JdbcTemplate (Spring 6.0.12, in-memory H2 2.2.224):

interface RowMapper<T> { T map(ResultSet rs) throws SQLException; }

static class MiniJdbc {
    private final DataSource ds;
    MiniJdbc(DataSource ds) { this.ds = ds; }

    <T> List<T> query(String sql, RowMapper<T> mapper) {
        try (Connection c = ds.getConnection();
             PreparedStatement ps = c.prepareStatement(sql);
             ResultSet rs = ps.executeQuery()) {
            List<T> out = new ArrayList<>();
            while (rs.next()) out.add(mapper.map(rs));
            return out;
        } catch (SQLException e) {
            throw new IllegalStateException("Query failed: " + e.getMessage(), e);
        }
    }
}
MiniJdbc (hand-written):
  Product[id=1, name=T-shirt, price=180000]
  Product[id=2, name=Running shoes, price=1450000]
JdbcTemplate (Spring):
  Product[id=1, name=T-shirt, price=180000]
  Product[id=2, name=Running shoes, price=1450000]
SQL error translated to DataAccessException:
  org.springframework.jdbc.BadSqlGrammarException

Both give the same result. The valuable part of a template is that you cannot forget to close the connection: try-with-resources lives in the skeleton, not in your code. Spring also adds something the mini version skips, which is translating SQLException into the DataAccessException hierarchy; the SQL statement above with a wrong table name returns BadSqlGrammarException, a subclass of it. The benefit of callbacks over inheritance, in our view (the Spring documentation does not state this as the official design reason), is that you do not need a new subclass for every query, and lambdas turn each step into a single line.

Template Method vs Strategy: inheritance or composition

The two patterns solve nearby problems: separating the fixed part from the changing part. Refactoring.guru summarizes it this way: Template Method is based on inheritance, works at the class level, and is fixed at compile time; Strategy is based on composition, works at the object level, and can be swapped at runtime. The article Strategy pattern with lambdas and functional interfaces covers the second approach in detail.

CriterionTemplate MethodStrategy
MechanismInherit an abstract classPass in an object or lambda
When the variant is chosenAt coding time or object constructionCan change while running
Number of variation pointsMany steps in one skeletonUsually one replaceable algorithm
Main riskTight coupling to the parent class, as in the mistakes aboveMany small objects to wire together
Fits whenThe skeleton is stable and many subclasses share one moldBehavior must change by configuration or context

A practical rule: if only one or two steps vary and you want to test each step on its own, pass a lambda (Strategy, or a JdbcTemplate-style callback). If many steps are bound together in an unchangeable sequence and the system naturally has a family of subclasses (report types, file import types), Template Method reads more cleanly. Many codebases combine both: a Template Method skeleton whose filter step accepts a Predicate.

When not to use Template Method

  • The skeleton is not stable yet. Every change to a step signature means editing every subclass. Wait until you have at least two or three real variants before extracting the skeleton.
  • The number of steps balloons. Refactoring.guru notes that Template Method gets harder to maintain as the number of steps grows. When the parent has eight or nine extension points, switch to composition.
  • The subclass already has another parent. Java allows only single class inheritance. If CsvExporter must extend a different framework class, this pattern is stuck, while callbacks are not.
  • You need to change behavior at runtime. Inheritance is fixed when the object is built; to change behavior based on user configuration, use Strategy.
  • There is only one variant. An abstract parent with a single subclass is unnecessary complexity.

Checklist for writing Template Method

  • Declare the template method final; make the steps protected.
  • Clearly separate required steps (abstract), steps with defaults, and hooks (empty body).
  • Never call an overridable step from a constructor.
  • Document each step's contract in Javadoc: what it receives, what it returns, and what must be preserved when overriding.
  • Check that the data passed between steps is consistent, like the footer(total) example above.
  • Keep the number of steps small; consider callbacks or Strategy when many variation points are independent.
  • Test the skeleton with a fake subclass that records the call order, like the log list in the demo.

Summary

Template Method puts the algorithm in the parent class and lets subclasses fill in the differing steps, which removes duplicated code and keeps execution order consistent. The price is tight coupling through inheritance: the three mistakes we ran show that constructors, default steps and final are the easiest places to go wrong. The JDK (InputStream, AbstractList) and Spring (JdbcTemplate) both use this idea, but Spring leans toward callbacks to avoid the burden of inheritance. The full source code is in code/s6-14/.

Next article

When you need to wrap an entire request into an object for queuing, logging or undoing, we move on to another pattern: Command pattern: command queues, undo and redo. You can also revisit Bridge and Flyweight pattern: separating abstraction and sharing state to compare ways of separating the fixed part from the changing part.