Template Method Pattern
The template method pattern defines the fixed skeleton of an algorithm in one place and lets callers plug in only the parts that vary. Spring uses this everywhere its class name ends in Template — JdbcTemplate, RestTemplate, RabbitTemplate, KafkaTemplate, TransactionTemplate. The template owns all the tedious, error-prone boilerplate (opening connections, handling errors, cleaning up resources); you supply the single variable step as a callback or lambda.
The boilerplate the template removes
Consider raw JDBC. Every query repeats the same dance: get a connection, create a statement, execute, iterate the result set, map each row, then close everything in the right order even on error.
// Without a template — the same ceremony around every single query.
Connection conn = dataSource.getConnection();
try (PreparedStatement ps = conn.prepareStatement("SELECT id, name FROM product WHERE id = ?")) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new Product(rs.getLong("id"), rs.getString("name"));
}
return null;
}
} finally {
conn.close();
}
The only part that differs between queries is the SQL and the row-to-object mapping. Everything else is boilerplate the template can own.
JdbcTemplate — the canonical example
JdbcTemplate holds the algorithm skeleton (connection handling, statement execution, exception translation, resource cleanup). You provide the SQL, the parameters, and a RowMapper — the variable step.
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import lombok.RequiredArgsConstructor;
@Repository
@RequiredArgsConstructor
public class ProductDao {
private final JdbcTemplate jdbc;
private static final RowMapper<Product> MAPPER = (rs, rowNum) ->
new Product(rs.getLong("id"), rs.getString("name"));
public Product findById(Long id) {
return jdbc.queryForObject(
"SELECT id, name FROM product WHERE id = ?", MAPPER, id);
}
public List<Product> findAll() {
return jdbc.query("SELECT id, name FROM product", MAPPER);
}
public int insert(Product p) {
return jdbc.update(
"INSERT INTO product (id, name) VALUES (?, ?)", p.id(), p.name());
}
}
The lambda (rs, rowNum) -> new Product(...) is the only logic you wrote. JdbcTemplate opens and closes the connection, translates SQLException into Spring’s unchecked DataAccessException hierarchy, and guarantees cleanup. Inject it after adding spring-boot-starter-jdbc and a DataSource.
The callback is the “primitive operation”
In classic GoF terms, the template’s query(...) method is the template method and your RowMapper is the primitive operation it calls back into. Spring expresses these primitives as functional interfaces, so they collapse to lambdas:
| Callback interface | Variable step it supplies |
|---|---|
RowMapper<T> | Map one result-set row to an object |
ResultSetExtractor<T> | Process the entire result set |
PreparedStatementSetter | Bind parameters to a statement |
ConnectionCallback<T> | Work directly with a Connection |
The wider *Template family
Every template applies the same pattern to a different resource:
| Template | Resource it manages | You supply |
|---|---|---|
JdbcTemplate | JDBC connections & statements | SQL + RowMapper |
RestTemplate | HTTP connections (legacy; prefer RestClient) | URL, method, body type |
TransactionTemplate | Transaction boundaries | The work to run in a tx |
RabbitTemplate | AMQP channels | Exchange, routing key, message |
KafkaTemplate | Kafka producer | Topic, key, value |
TransactionTemplate is the programmatic alternative to @Transactional and shows the pattern clearly — the template begins and commits/rolls back; you supply only the body:
import org.springframework.transaction.support.TransactionTemplate;
@Service
@RequiredArgsConstructor
public class TransferService {
private final TransactionTemplate txTemplate;
private final AccountDao accounts;
public void transfer(Long from, Long to, BigDecimal amount) {
txTemplate.executeWithoutResult(status -> { // skeleton: begin/commit
accounts.debit(from, amount); // your variable step
accounts.credit(to, amount);
});
}
}
Tip: Reach for
TransactionTemplatewhen you need fine-grained, programmatic control over a transaction’s boundary — for example committing partway through a long job. For ordinary cases the declarative@Transactional(itself proxy-based) is cleaner.
Note:
RestTemplateis in maintenance mode. For new HTTP client code preferRestClient(synchronous) orWebClient(reactive); both also expose template-method-style callbacks but with a fluent builder API.
Because the template owns resource lifecycle and error handling, you cannot forget to close a connection or mistranslate an exception — the pattern makes the safe path the only path.