Skip to content
Spring Boot sb design-patterns 3 min read

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 TemplateJdbcTemplate, 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 interfaceVariable step it supplies
RowMapper<T>Map one result-set row to an object
ResultSetExtractor<T>Process the entire result set
PreparedStatementSetterBind 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:

TemplateResource it managesYou supply
JdbcTemplateJDBC connections & statementsSQL + RowMapper
RestTemplateHTTP connections (legacy; prefer RestClient)URL, method, body type
TransactionTemplateTransaction boundariesThe work to run in a tx
RabbitTemplateAMQP channelsExchange, routing key, message
KafkaTemplateKafka producerTopic, 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 TransactionTemplate when 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: RestTemplate is in maintenance mode. For new HTTP client code prefer RestClient (synchronous) or WebClient (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.

Last updated June 13, 2026
Was this helpful?