Template Method Pattern: Spring’s Most Used Yet Least Explained Design Pattern

The article explains how the Template Method pattern pervades Spring’s core classes—such as JdbcTemplate, AbstractApplicationContext, and various *Template classes—by fixing the overall algorithm flow while delegating the variable steps to subclasses, and contrasts it with the Strategy pattern.

Tinker Programmer
Tinker Programmer
Tinker Programmer
Template Method Pattern: Spring’s Most Used Yet Least Explained Design Pattern

The Template Method pattern is the design pattern that appears most frequently in Spring’s source code, showing up in classes like JdbcTemplate, RedisTemplate, RestTemplate, AbstractApplicationContext and AbstractBeanFactory.

From a real‑world duplicated‑code example

A typical requirement is to export a report in Excel, CSV and PDF formats. A naïve implementation creates three separate services that repeat the same four steps (query, permission check, preprocessing, logging) and only differ in the fourth step that writes the file. This leads to bugs when a change (e.g., permission logic) must be applied to all formats.

public class ExcelExportService {
    public void export(Long reportId) {
        // step 1: query data
        ReportData data = reportDao.findById(reportId);
        // step 2: permission check
        permissionService.checkExportPermission(data);
        // step 3: preprocess data
        dataProcessor.preprocess(data);
        // step 4: write Excel
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("报表");
        fillSheet(sheet, data);
        workbook.write(outputStream);
        // step 5: log export
        exportLogDao.save(new ExportLog(reportId, "EXCEL", LocalDateTime.now()));
    }
}

Template Method: putting the invariant in the parent, the variant in subclasses

The pattern solves the problem of a batch of objects that share a highly similar processing flow, with only a few steps differing. Its core structure consists of three elements:

Template method : a final method that defines the overall algorithm and locks the step order.

Abstract steps : abstract methods that subclasses must implement for the varying parts.

Hook methods : optional methods with a default (often empty) implementation that subclasses may override.

Basic skeleton

public abstract class ReportExporter {
    // template method – final, order fixed
    public final void export(Long reportId) {
        ReportData data = reportDao.findById(reportId);
        permissionService.checkExportPermission(data);
        dataProcessor.preprocess(data);
        // variable part
        doExport(data);
        exportLogDao.save(new ExportLog(reportId, getFormatName(), LocalDateTime.now()));
    }
    // abstract steps – must be provided by subclasses
    protected abstract void doExport(ReportData data);
    protected abstract String getFormatName();
}

Subclasses only need to implement the varying parts:

@Service
public class ExcelExporter extends ReportExporter {
    @Override
    protected void doExport(ReportData data) {
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("报表");
        fillSheet(sheet, data);
        workbook.write(outputStream);
    }
    @Override
    protected String getFormatName() { return "EXCEL"; }
}

@Service
public class CsvExporter extends ReportExporter {
    @Override
    protected void doExport(ReportData data) {
        CsvWriter writer = new CsvWriter(outputStream, StandardCharsets.UTF_8);
        for (ReportRow row : data.getRows()) { writer.writeRecord(row.toArray()); }
        writer.close();
    }
    @Override
    protected String getFormatName() { return "CSV"; }
}

Hook method: adding flexibility

A hook method provides an optional extension point. The base class supplies a default implementation; a subclass can override it when needed.

public abstract class ReportExporter {
    public final void export(Long reportId) {
        ReportData data = reportDao.findById(reportId);
        permissionService.checkExportPermission(data);
        dataProcessor.preprocess(data);
        // hook controls a branch
        if (needWatermark()) { watermarkService.apply(data); }
        doExport(data);
        exportLogDao.save(new ExportLog(reportId, getFormatName(), LocalDateTime.now()));
    }
    protected abstract void doExport(ReportData data);
    protected abstract String getFormatName();
    // hook method – default does nothing
    protected boolean needWatermark() { return false; }
}

public class PdfExporter extends ReportExporter {
    @Override
    protected boolean needWatermark() { return true; }
    @Override
    protected void doExport(ReportData data) { /* PDF specific logic */ }
    @Override
    protected String getFormatName() { return "PDF"; }
}

Spring source examples

JdbcTemplate encapsulates the fixed skeleton of JDBC operations, exposing only two variation points: setting SQL parameters and mapping result rows.

Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
    conn = dataSource.getConnection(); // get connection
    pstmt = conn.prepareStatement(sql); // create statement
    setParameters(pstmt, params); // ← variation point
    rs = pstmt.executeQuery();
    return mapResult(rs); // ← variation point
} catch (SQLException e) {
    // exception handling
} finally {
    // close rs, pstmt, conn in correct order
}

Calling code supplies a RowMapper lambda to implement the abstract step that maps a row to an object:

List<User> users = jdbcTemplate.query(
    "SELECT * FROM users WHERE age > ?",
    new Object[]{18},
    (rs, rowNum) -> {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        return user;
    }
);

AbstractApplicationContext.refresh() is the template method that drives Spring container startup. It consists of 12 fixed steps, with abstract methods such as obtainFreshBeanFactory() (implemented differently by ClassPathXmlApplicationContext and AnnotationConfigApplicationContext) and hook methods like onRefresh() (overridden by ServletWebServerApplicationContext to start an embedded Tomcat).

public void refresh() throws BeansException {
    // 12 steps – order fixed
    prepareRefresh();
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // abstract
    prepareBeanFactory(beanFactory);
    postProcessBeanFactory(beanFactory); // hook
    invokeBeanFactoryPostProcessors(beanFactory);
    registerBeanPostProcessors(beanFactory);
    initMessageSource();
    initApplicationEventMulticaster();
    onRefresh(); // hook – critical for web context
    registerListeners();
    finishBeanFactoryInitialization(beanFactory);
    finishRefresh();
}

protected abstract ConfigurableListableBeanFactory obtainFreshBeanFactory();
protected void onRefresh() throws BeansException { }

Template Method vs. Strategy

Implementation : Template Method uses inheritance; Strategy uses composition.

Granularity of change : Template Method fixes the overall algorithm, only a few steps vary; Strategy can replace the whole algorithm.

Extension : Add a subclass vs. add a strategy implementation class.

Runtime replacement : Not possible for Template Method (decided at compile time); possible for Strategy (inject different strategy at runtime).

Coupling : Strong parent‑child coupling vs. weak interface coupling.

Choose Template Method when the process steps are fixed and only a few details differ (e.g., report export). Choose Strategy when the entire processing logic may vary (e.g., different discount calculations).

Conclusion

The essence of the Template Method pattern is succinctly: put the invariant parts into the parent class and leave the variant parts to subclasses. In Spring source, classes with an Abstract prefix or a Template suffix (e.g., JdbcTemplate, RedisTemplate) are strong indicators that this pattern is at work.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backenddesign patternsJavaSpringtemplate method
Tinker Programmer
Written by

Tinker Programmer

Solving problems with code, sharing practical tech insights, and leveling up together!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.