📋 Template Method Design Pattern
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
🌍 Real-World Analogy
Analogy — Building a House
Every house follows the same construction template: lay foundation, build walls, install roof, add windows, paint. But a wooden house uses timber frames while a brick house uses masonry. The overall process (template) stays the same; only specific steps differ between types. The architect defines the template; the builders customize the steps.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
T["📐 Build House"] -->|"step 1"| S1["🧱 Foundation"] -->|"step 2"| S2["🏗️ Walls"] -->|"step 3"| S3["🏠 Roof"] -->|"step 4"| S4["🎨 Paint"]
S2 -.->|"wooden"| W["🪵 Timber"]
S2 -.->|"brick"| B["🧱 Masonry"]
style T fill:#FFF8E1,stroke:#F9A825,stroke-width:2px,color:#000
style S1 fill:#E3F2FD,stroke:#1565C0,color:#000
style S2 fill:#E3F2FD,stroke:#1565C0,color:#000
style S3 fill:#E3F2FD,stroke:#1565C0,color:#000
style S4 fill:#E3F2FD,stroke:#1565C0,color:#000
style W fill:#FFF3E0,stroke:#E65100,color:#000
style B fill:#FCE4EC,stroke:#C62828,color:#000 🏗️ Pattern Structure
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
Abstract[["📋 Abstract Class"]] -->|"defines"| Template(["🔲 Template Method"])
ConcreteA{{"🏠 Concrete A"}} -->|"extends"| Abstract
ConcreteB{{"🏢 Concrete B"}} -->|"extends"| Abstract
style Abstract fill:#FFF3E0,stroke:#E65100,color:#000
style Template fill:#FFF8E1,stroke:#F9A825,color:#000
style ConcreteA fill:#E3F2FD,stroke:#1565C0,color:#000
style ConcreteB fill:#E3F2FD,stroke:#1565C0,color:#000 UML Class Diagram
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
class DataProcessor {
<<abstract>>
+process(String source) void
#extractData()* String
#parseData(String rawData)* List~Record~
#generateReport(List~Record~ records)* void
#filterData(List~Record~ records) List~Record~
#analyzeData(List~Record~ records) void
}
class CsvDataProcessor {
#extractData() String
#parseData(String rawData) List~Record~
#generateReport(List~Record~ records) void
}
class ApiDataProcessor {
#extractData() String
#parseData(String rawData) List~Record~
#filterData(List~Record~ records) List~Record~
#generateReport(List~Record~ records) void
}
CsvDataProcessor --|> DataProcessor
ApiDataProcessor --|> DataProcessor ❓ The Problem
You have multiple classes that follow a similar algorithm but differ in specific steps:
- Code is duplicated across classes for the shared steps
- Changing the algorithm structure requires modifying every implementation
- No way to enforce that subclasses follow the correct order of operations
- Adding a new variant requires copying the entire algorithm
Example: Data mining from different file formats (CSV, PDF, DOCX) — the overall process (open, extract, parse, analyze, report) is the same but extraction differs.
Without This Pattern
public class CsvDataProcessor {
public void process(String source) {
System.out.println("Opening: " + source); // duplicated
String raw = extractCsvData();
List<Record> records = parseCsvData(raw);
System.out.println("Analyzing " + records.size()); // duplicated
generateCsvReport(records);
System.out.println("Cleanup complete"); // duplicated
}
}
public class ApiDataProcessor {
public void process(String source) {
System.out.println("Opening: " + source); // SAME as above
String raw = fetchFromApi();
List<Record> records = parseJsonData(raw);
System.out.println("Analyzing " + records.size()); // SAME
generateApiReport(records);
System.out.println("Cleanup complete"); // SAME
}
// If the algorithm order changes, you must fix BOTH classes.
}
- Code duplication — shared steps (open, analyze, cleanup) are copy-pasted across every variant; a bug fix must be applied N times
- No enforced order — nothing prevents a subclass from calling steps in the wrong order (e.g., reporting before parsing)
- Violates DRY — adding a new step (e.g., validation) requires editing every processor class identically
- Divergence risk — over time, duplicated algorithms drift apart as developers modify one but forget the other
- Pain point: A security team mandates adding an audit log step between parse and analyze. Without a template, you must find and modify every processor variant — and if you miss one, it ships without the audit
✅ The Solution
The Template Method pattern puts the algorithm in a base class method (the template):
- Define the template method in the abstract class — it calls steps in order
- Make the template method
finalto prevent subclasses from changing the structure - Mark customizable steps as
abstract— subclasses must implement these - Provide hooks — optional steps with default (often empty) implementations
💻 Implementation
// Abstract class with template method
public abstract class DataProcessor {
// Template method — defines the algorithm skeleton
public final void process(String source) {
openSource(source);
String rawData = extractData();
List<Record> records = parseData(rawData);
List<Record> filtered = filterData(records); // Hook with default
analyzeData(filtered);
generateReport(filtered);
cleanup();
}
// Concrete steps (shared)
private void openSource(String source) {
System.out.println("📂 Opening: " + source);
}
private void cleanup() {
System.out.println("🧹 Cleanup complete");
}
// Abstract steps — subclasses MUST implement
protected abstract String extractData();
protected abstract List<Record> parseData(String rawData);
protected abstract void generateReport(List<Record> records);
// Hook — optional override (default does nothing)
protected List<Record> filterData(List<Record> records) {
return records; // No filtering by default
}
// Hook — optional override
protected void analyzeData(List<Record> records) {
System.out.println("📊 Analyzing " + records.size() + " records");
}
}
// Concrete implementation — CSV
public class CsvDataProcessor extends DataProcessor {
@Override
protected String extractData() {
System.out.println("📄 Extracting CSV data");
return "name,age,city\nAlice,30,NYC\nBob,25,LA";
}
@Override
protected List<Record> parseData(String rawData) {
String[] lines = rawData.split("\n");
String[] headers = lines[0].split(",");
List<Record> records = new ArrayList<>();
for (int i = 1; i < lines.length; i++) {
records.add(new Record(headers, lines[i].split(",")));
}
return records;
}
@Override
protected void generateReport(List<Record> records) {
System.out.println("📊 CSV Report: " + records.size() + " rows processed");
}
}
// Concrete implementation — JSON API
public class ApiDataProcessor extends DataProcessor {
@Override
protected String extractData() {
System.out.println("🌐 Fetching data from REST API");
return "[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]";
}
@Override
protected List<Record> parseData(String rawData) {
// Parse JSON array
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(rawData, new TypeReference<>() {});
}
@Override
protected List<Record> filterData(List<Record> records) {
// Override hook — filter inactive records
return records.stream()
.filter(Record::isActive)
.collect(Collectors.toList());
}
@Override
protected void generateReport(List<Record> records) {
System.out.println("📈 API Report: " + records.size() + " active records");
}
}
// Usage
public class Main {
public static void main(String[] args) {
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process("data/users.csv");
DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process("https://api.example.com/users");
}
}
// Abstract game character with template
public abstract class GameCharacter {
// Template method
public final void performTurn() {
beginTurn();
if (canAttack()) { // Hook
attack();
}
move();
useSpecialAbility(); // Abstract
endTurn();
}
// Concrete steps
private void beginTurn() { System.out.println("--- Turn Start ---"); }
private void endTurn() { System.out.println("--- Turn End ---\n"); }
// Abstract steps
protected abstract void attack();
protected abstract void move();
protected abstract void useSpecialAbility();
// Hook — default returns true, can be overridden
protected boolean canAttack() { return true; }
}
public class Warrior extends GameCharacter {
@Override
protected void attack() {
System.out.println("⚔️ Warrior swings sword!");
}
@Override
protected void move() {
System.out.println("🚶 Warrior advances 3 tiles");
}
@Override
protected void useSpecialAbility() {
System.out.println("🛡️ Warrior raises shield — defense +50%");
}
}
public class Mage extends GameCharacter {
private int mana = 0;
@Override
protected void attack() {
System.out.println("🔥 Mage casts fireball!");
}
@Override
protected void move() {
System.out.println("✨ Mage teleports 5 tiles");
}
@Override
protected void useSpecialAbility() {
System.out.println("💫 Mage regenerates mana");
mana += 20;
}
@Override
protected boolean canAttack() {
return mana >= 10; // Hook — can't attack without mana
}
}
🎯 When to Use
- When you have multiple classes with similar algorithms that differ only in certain steps
- When you want to enforce a specific order of operations
- When you want to let subclasses extend specific steps without changing structure
- When you want to eliminate code duplication by pulling shared logic into a base class
- When you need hooks — optional extension points for subclasses
🏭 Real-World Examples
| Framework/Library | Usage |
|---|---|
java.util.AbstractList | get() and size() are abstract; indexOf(), iterator() are template methods |
java.io.InputStream | read() is abstract; read(byte[], int, int) is the template |
Spring JdbcTemplate | Template for JDBC operations — you provide the query and row mapper |
Spring RestTemplate | Template for HTTP calls |
Spring AbstractController | handleRequest() is the template |
JUnit TestCase | setUp(), runTest(), tearDown() is the template |
Servlet HttpServlet | service() is template; doGet(), doPost() are hooks |
⚠️ Pitfalls
Common Mistakes
- Fragile base class — Changes to the abstract class can break all subclasses. Keep the template stable.
- Too many abstract steps — If every step is abstract, you lose the benefit. The template should have shared logic.
- Forgetting
final— If the template method isn'tfinal, subclasses can override it and break the algorithm. - Deep inheritance — Template Method relies on inheritance; deep hierarchies become brittle. Prefer one level.
- Confusing hooks vs. abstract — Make it clear which steps are optional (hooks) vs. required (abstract).
📝 Key Takeaways
Summary
- Template Method defines the algorithm skeleton in a base class; subclasses fill in the details
- Uses inheritance — the template method calls abstract/hook methods overridden by subclasses
- Mark template methods as
finalto prevent subclasses from changing the workflow - Hooks are optional extension points with default implementations
- Template Method uses inheritance (compile-time); Strategy uses composition (runtime) — pick based on flexibility needs
- The "Hollywood Principle" — don't call us, we'll call you (the framework calls your code)