Intent
Define the skeleton of an algorithm in a base class, deferring some steps to subclasses via abstract or hook methods. The template method is declared final — subclasses fill in variable steps without changing the algorithm structure.
When to reach for it: Multiple classes share the same algorithm outline (open connection, execute query, close connection) but differ in specific steps (what SQL to run, how to map results). Extract the invariant skeleton to an abstract class; subclasses provide the variant steps.
Hook methods vs abstract methods: Abstract methods MUST be overridden by subclasses (required steps). Hook methods have a default empty/passthrough implementation — subclasses override them OPTIONALLY (extension points). Good template classes use both strategically.
JavaData Export Pipeline + JdbcTemplate Internals
// TEMPLATE: abstract base class defines the algorithm skeleton
public abstract class DataExporter<T> {
// THE TEMPLATE METHOD — final: cannot be overridden, algorithm structure is locked
public final ExportResult export(ExportRequest request) {
log.info("Starting export: type={}", getExportType());
validate(request); // step 1: fixed (provided by base class)
List<T> raw = fetchData(request); // step 2: abstract — subclass must provide
List<T> filtered = applyFilters(raw); // step 3: hook — optional override
String output = format(filtered); // step 4: abstract — subclass must provide
ExportResult result = writeOutput(output, request); // step 5: fixed
onExportComplete(result); // step 6: hook — optional override
log.info("Export complete: rows={} file={}", result.getRowCount(), result.getFilename());
return result;
}
// ABSTRACT steps: subclasses MUST implement
protected abstract List<T> fetchData(ExportRequest request);
protected abstract String format(List<T> data);
protected abstract String getExportType();
// HOOK steps: optional overrides (default = no-op or passthrough)
protected List<T> applyFilters(List<T> data) { return data; } // passthrough by default
protected void onExportComplete(ExportResult r) { } // no-op by default
// FIXED steps: fully implemented in base class, shared by all subclasses
private void validate(ExportRequest req) {
Objects.requireNonNull(req, "Request cannot be null");
if (req.getFromDate().isAfter(req.getToDate())) {
throw new IllegalArgumentException("fromDate must be before toDate");
}
}
private ExportResult writeOutput(String content, ExportRequest req) {
String filename = req.getBaseFilename() + "_" + Instant.now().getEpochSecond();
s3Client.putObject(bucket, filename, content);
return ExportResult.of(filename, content.split("\n").length);
}
}
// CONCRETE 1: Order CSV exporter
@Service
public class OrderCsvExporter extends DataExporter<Order> {
private final OrderRepository repo;
@Override
protected List<Order> fetchData(ExportRequest req) {
return repo.findByDateRange(req.getFromDate(), req.getToDate());
}
@Override
protected String format(List<Order> orders) {
var sb = new StringBuilder("id,customerId,status,total,createdAt\n");
orders.forEach(o -> sb.append(
String.format("%s,%s,%s,%.2f,%s\n", o.getId(), o.getCustomerId(),
o.getStatus(), o.getTotal(), o.getCreatedAt())));
return sb.toString();
}
@Override
protected List<Order> applyFilters(List<Order> data) {
return data.stream().filter(o -> o.getStatus() != OrderStatus.CANCELLED).toList();
}
@Override
protected void onExportComplete(ExportResult r) {
auditLog.record("ORDER_EXPORT", r.getFilename(), r.getRowCount());
}
protected String getExportType() { return "ORDER_CSV"; }
}
// CONCRETE 2: Financial report PDF exporter — different steps, same template
@Service
public class FinancialPdfExporter extends DataExporter<Transaction> {
@Override
protected List<Transaction> fetchData(ExportRequest req) {
return txRepo.findByPeriod(req.getFromDate(), req.getToDate());
}
@Override
protected String format(List<Transaction> txs) {
return pdfGenerator.generate(txs); // totally different formatting
}
protected String getExportType() { return "FINANCIAL_PDF"; }
// applyFilters and onExportComplete use default (passthrough / no-op)
}
// HOW SPRING's JdbcTemplate IS Template Method:
// The template method: JdbcTemplate.query(sql, RowMapper, args...)
// FIXED steps (handled by JdbcTemplate):
// → getConnection() from pool
// → create PreparedStatement
// → set parameters
// → execute query
// → translate SQLException to DataAccessException
// → close resources (finally block)
// YOUR variable steps:
// → the SQL string
// → the RowMapper (how to turn each row into a Java object)
@Repository
public class OrderJdbcRepository {
private final JdbcTemplate jdbc;
public List findHighValueOrders(BigDecimal minAmount) {
return jdbc.query( // ← template method
"SELECT id, total, status FROM orders WHERE total >= ?", // YOUR SQL
(rs, rowNum) -> new OrderSummary( // YOUR RowMapper
rs.getObject("id", UUID.class),
rs.getBigDecimal("total"),
OrderStatus.valueOf(rs.getString("status"))
),
minAmount // YOUR parameters
);
// JdbcTemplate handles everything else — open, prepare, execute, close
}
}
🔩 Where Template Method Is Used in Java Frameworks
Spring JdbcTemplate
query(sql, RowMapper, args) is the template. Spring handles connection lifecycle, error translation, resource cleanup. You provide SQL and RowMapper — the variable steps.
Spring AbstractController
handleRequest() is the template. It calls checkAndPrepare(), then handleRequestInternal() — your abstract method. Session checks, locale handling, method routing are the fixed steps.
Spring Batch ItemReader/Processor/Writer
Spring Batch's chunk processing is a template: read → process → write. The Step framework manages transactions, skip logic, retry — you provide the three implementations. AbstractItemStreamItemReader has doRead() as the abstract step.
Spring Security AbstractSecurityInterceptor
Template for all security interceptors. Fixed steps: obtain security object, get ConfigAttributes, call AccessDecisionManager. Abstract method getSecureObjectClass() varies by subclass (method security vs URL security).
Hibernate AbstractEntityPersister
Template for entity persistence: generate SQL, bind parameters, execute, extract results. Subclasses (SingleTableEntityPersister, JoinedSubclassEntityPersister) provide SQL generation strategies for different inheritance mappings.
Spring RestTemplate (deprecated) / WebClient
RestTemplate.execute() is a template: create request, set headers, write body, send, extract response. You provide RequestCallback and ResponseExtractor — the variable steps in the fixed HTTP request/response lifecycle.
🎯 Interview Questions — Template Method
How does JdbcTemplate use Template Method? Walk through what happens when you call query().
When you call
jdbcTemplate.query(sql, rowMapper, args):
- JdbcTemplate gets a
Connection from the DataSource pool (FIXED) - Creates a
PreparedStatement using your SQL (YOUR variable step) - Calls
ArgumentPreparedStatementSetter.setValues(ps) with your args (FIXED) - Executes the statement (FIXED)
- Iterates the
ResultSet — calls your RowMapper.mapRow(rs, rowNum) for each row (YOUR variable step) - Wraps any
SQLException into Spring's DataAccessException hierarchy (FIXED) - Closes PreparedStatement and returns Connection to pool in finally (FIXED)
You never write steps 1, 3, 4, 6, 7. Template Method eliminates the boilerplate that every JDBC call requires.
Template Method vs Strategy — both vary an algorithm. What's the key difference?
Template Method uses inheritance: The algorithm skeleton is in the abstract class. Variable steps are methods overridden by subclasses. You create a new class to vary behavior. The relationship is compile-time and fixed. Once you subclass DataExporter, the template is fixed for that class.
Strategy uses composition: The algorithm is encapsulated in a separate object passed to the context. You pass a different object at runtime to change behavior. No inheritance required. More flexible — you can swap strategies on the same object at runtime.
Rule of thumb: Template Method for framework extension points (you write code that the framework calls). Strategy for runtime behavior selection (you pick which algorithm to use when calling). Template Method is framework-designer's tool; Strategy is application-developer's tool.
Why should the template method be declared final?
The template method defines the invariant algorithm structure — it's the contract the framework provides. If subclasses can override the template method, they can break the contract: skip validation, change the order of steps, or bypass resource cleanup. Declaring it final enforces the Hollywood Principle: "Don't call us, we'll call you." The framework (parent) controls the flow; subclasses (you) provide the content. JdbcTemplate, Spring Batch, and other Spring templates all follow this pattern — they control the lifecycle, you control the domain logic within designated extension points.