Thymeleaf can render HTML in a batch job, command-line program, mail service, or
report pipeline without Spring MVC. A standalone application configures a
template resolver and TemplateEngine, supplies a context, and writes the
rendered output to its destination.
Choose the Integration Level #
A non-Spring program needs the core Thymeleaf artifact:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>A Spring application can use thymeleaf-spring6 and SpringTemplateEngine for
Spring EL, MessageSource, conversion, and Spring-aware link behavior. Spring
Boot dependency management should choose the matching Thymeleaf version.
spring-boot-starter-thymeleaf is convenient when those integrations are
needed, but it is not required to run the core engine and does not require MVC.
Configure the Resolver and Engine #
Select a resolver that matches template ownership. A
ClassLoaderTemplateResolver works for immutable templates packaged in a JAR;
a FileTemplateResolver works for operator-managed files:
var resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("reports/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
resolver.setCacheable(true);
var engine = new TemplateEngine();
engine.setTemplateResolver(resolver);Configure an engine once and then share it. Thymeleaf documents a fully
configured TemplateEngine as thread-safe, while changing configuration after
processing begins is unsupported. Enable caching for stable production
templates. Disable it, or configure bounded cache validity, only when files
must be re-read during development or operation.
Character encoding on the resolver controls how template bytes are decoded.
The encoding used by the output Writer independently controls generated file
bytes; set both explicitly.
Render Heterogeneous Report Data #
Expose domain-shaped values instead of constructing HTML in Java:
record WarningRow(String orderId, String message) {}
record DailyReport(List<OrderSummary> orders, List<WarningRow> warnings) {}
var context = new Context(Locale.ENGLISH);
context.setVariable("report", report);
context.setVariable("generatedAt", generatedAt);
engine.process("daily-report", context, writer);A template can render independent sections with different row types:
<section>
<h2>Warnings</h2>
<table>
<tr th:each="warning : ${report.warnings}">
<td th:text="${warning.orderId}">order</td>
<td th:text="${warning.message}">message</td>
</tr>
</table>
</section>th:text escapes text for the output mode. th:utext emits unescaped markup
and must not receive untrusted content. HTML template mode parses and writes
HTML-aware markup; choose XML, text, JavaScript, or CSS modes only when the
target format requires their different rules.
Fragments and Testability #
Declare reusable markup with th:fragment and compose it through th:insert or
th:replace. Fragment parameters keep shared tables and headers independent of
global variable names. Multiple resolvers can be ordered and restricted by
resolvable patterns, preventing a broad filesystem resolver from shadowing
packaged templates unintentionally.
Keep rendering as a pure service that accepts a report model and a Writer.
Tests can use StringWriter, parse the result with an HTML parser, and assert
structure and escaping. Resolver tests should also verify missing templates and
non-ASCII content.
Manage Output Resources #
Write with an explicit charset and close the writer:
Path temporary = Files.createTempFile(outputDirectory, "daily-", ".html");
try (Writer writer = Files.newBufferedWriter(
temporary, StandardCharsets.UTF_8,
StandardOpenOption.TRUNCATE_EXISTING)) {
engine.process("daily-report", context, writer);
}
Files.move(temporary, destination,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);Create temporary files in the destination filesystem when an atomic move is required. Clean up the temporary path after rendering or upload failure, and do not publish the destination until processing succeeds. For object storage, render to a bounded buffer or stream through an upload API whose retry and partial-failure behavior is explicit.
Comments
Read or comment on GitHub (sign-in required to comment)