Thymeleaf View Support
The juneau-rest-server-view-thymeleaf module adds Thymeleaf
view-rendering to juneau-rest-server without bleeding the Thymeleaf engine dependency into
the core.
This page covers the Thymeleaf-specific bridge. For the engine-agnostic
Viewinterface itself, see the 10.0.0 release notes under thejuneau-rest-serversection. The sibling JSP View Support, Mustache View Support, and FreeMarker View Support pages cover the JSP, Mustache, and FreeMarker bridges with the same shape.
Why Thymeleaf?
Thymeleaf is the default web view technology for Spring Boot — it's the engine Spring
Boot autoconfigures when spring-boot-starter-thymeleaf is on the classpath, and it's the
template engine Spring's reference docs recommend over JSP for embedded servlet containers.
It also has zero servlet-container dependencies: the core engine renders directly to a
java.io.Writer and works under MockRest, Jetty microservices, and Spring Boot equally well.
Module contents
| Class | Role |
|---|---|
View (in juneau-rest-server core) | Engine-agnostic contract: getTemplateName(), getAttributes(), getResponseHeaders(). |
ThymeleafMixin | Mixin. Mounts /thymeleaf/* for raw .html template requests; registers ThymeleafViewRenderer on the response-processor chain. Builder: basePath(String) (default /), cacheTemplates(boolean) (default true), templateMode(TemplateMode) (default HTML). Builder implements the shared ViewMixinBuilder contract. |
ThymeleafView | View implementation. Immutable; fluent: ThymeleafView.of("hello").attr("name", name).header("Cache-Control", "no-store"). |
ThymeleafViewRenderer | ResponseProcessor that detects ThymeleafView returns and asks the configured org.thymeleaf.TemplateEngine to process(templateName, context, writer) directly onto the response. |
Engine-agnostic packaging
juneau-rest-server-view-thymeleaf ships only org.thymeleaf:thymeleaf in provided
scope. No engine is bundled with the bridge module. Consumers add the engine matching their
deployment.
Choosing a TemplateEngine
| Deployment | Recommended setup | Maven coordinates |
|---|---|---|
| Spring Boot | Spring Boot autoconfigured SpringTemplateEngine (extends TemplateEngine) | org.springframework.boot:spring-boot-starter-thymeleaf |
| Juneau microservice / Jetty | Bridge-default TemplateEngine (built on first use, anchored on the importer's classloader) | org.thymeleaf:thymeleaf |
| Custom resolvers / dialects | User-supplied @Bean TemplateEngine (e.g. FileTemplateResolver, custom Spring MessageSource, Spring Security dialect) | Whatever you need on top of org.thymeleaf:thymeleaf |
The bridge picks up a TemplateEngine bean from the request's BeanStore via
BeanStore.getBean(TemplateEngine.class) first; if no engine bean is registered, it
constructs a default TemplateEngine with a single ClassLoaderTemplateResolver configured
with prefix=basePath, suffix=".html", templateMode=HTML, and cacheable=true.
When no Thymeleaf engine is on the classpath, the renderer surfaces a clear diagnostic naming the missing dependency:
No Thymeleaf engine is available on the classpath. Add one of:
- org.springframework.boot:spring-boot-starter-thymeleaf (Spring Boot autoconfig)
- org.thymeleaf:thymeleaf (Juneau microservice / Jetty)
Or register a custom @Bean TemplateEngine that picks up your preferred resolvers.
See https://juneau.apache.org/docs/topics/ThymeleafViewSupport for the full matrix.
Hello-world
Maven
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-view-thymeleaf</artifactId>
<version>${juneau.version}</version>
</dependency>
<!-- pick ONE engine setup -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>
Resource layout
src/main/resources/
templates/
hello.html
Thymeleaf template (hello.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Hello</title></head>
<body>
<p>Hello, <span th:text="${name}">name</span>!</p>
</body>
</html>
REST resource — View-return dispatch
import org.apache.juneau.http.*;
import org.apache.juneau.rest.server.*;
import org.apache.juneau.rest.server.servlet.*;
import org.apache.juneau.rest.server.view.*;
import org.apache.juneau.rest.server.view.thymeleaf.*;
@Rest(path="/app", mixins=ThymeleafMixin.class)
public class AppResource extends RestServlet {
@Bean
ThymeleafMixin thymeleaf() {
return ThymeleafMixin.create()
.basePath("/templates/")
.cacheTemplates(false) // dev convenience; default true is production-safe
.build();
}
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return ThymeleafView.of("hello").attr("name", name);
}
}
GET /app/hello/world returns Hello, world! — ThymeleafViewRenderer intercepts the
ThymeleafView return, populates a Thymeleaf Context from the view's attributes, and asks
the active TemplateEngine to render /templates/hello.html directly onto the response.
Typed handler integration
To use ThymeleafView from a host-class @RestGet without adopting the full mixin, add
ThymeleafViewRenderer.class directly to the host's responseProcessors:
@Rest(responseProcessors={ThymeleafViewRenderer.class})
public class AppResource extends BasicRestServlet {
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return ThymeleafView.of("templates/hello.html").attr("name", name);
}
}
The ResponseProcessorList partition pass (added in 10.0.0) automatically repositions
ThymeleafViewRenderer to run before SerializedPojoProcessor. Because Thymeleaf renders
directly to a Writer with zero servlet-container dependencies, attribute interpolation
works under MockRest — verified end-to-end by
ThymeleafView_TypedHandler_Test#a04_renderInterpolatesAttributesFromTypedHandler.
REST resource — raw-template mount
The mixin also installs a default /thymeleaf/* mount that renders raw .html templates
under the configured base path. With basePath("/templates/"), a request for
GET /app/thymeleaf/about renders /templates/about.html directly — no Java handler
required. The handler strips a trailing .html so /app/thymeleaf/about.html works too.
The greedy /* handler is excluded from the generated Swagger / OpenAPI spec via
@OpSwagger(ignore=true) since the path isn't API-meaningful.
Configurable mount path (SVL)
The default mount /thymeleaf/* can be overridden via the SVL variable
${juneau.thymeleaf.path:thymeleaf} — set via system property
(-Djuneau.thymeleaf.path=views), environment variable (JUNEAU_THYMELEAF_PATH=views), or
Config key (juneau.thymeleaf.path = views) to change the runtime mount without
subclassing.
Path-traversal protection
The raw-template handler funnels every user-supplied @Path("/*") String path through
FileUtils.resolveVirtualPathSafely(String, String)
and rejects any .. traversal that escapes the configured basePath with HTTP 403. Both
direct (/thymeleaf/../secret) and nested (/thymeleaf/a/b/../../../secret) traversal
attempts are blocked at the handler boundary before reaching the engine resolver. This is
the same hardening the JSP bridge applies; see
ThymeleafMixin_PathTraversal_Test
for the canonical coverage.
Spring Boot integration
Spring Boot's reference docs explicitly recommend Thymeleaf as the default web view
technology — there is no awkward fat-jar resource-layout dance (Thymeleaf doesn't depend on
servlet semantics; the engine just resolves classpath:/templates/*.html directly), and
Spring Boot autoconfigures a SpringTemplateEngine (which extends TemplateEngine) that
the bridge picks up automatically through BeanStore.getBean(TemplateEngine.class).
@Bean registration
@Configuration
public class AppConfig {
@Bean
public ThymeleafMixin thymeleaf() {
return ThymeleafMixin.create()
.basePath("/templates/")
.build();
}
}
The Spring BeanStore adapter resolves the bean through
ApplicationContext.getBean(ThymeleafMixin.class); no additional plumbing is
required.
Renderer ordering (resolved in 10.0.0)
ThymeleafViewRenderer implements
ViewRenderer and is
automatically positioned before SerializedPojoProcessor by the ResponseProcessorList
partition pass. A @RestOp method returning ThymeleafView reaches the renderer
without any extra configuration — the mixin registers the renderer and the framework ensures
ordering. MockRest-level typed-handler integration is verified by
ThymeleafView_TypedHandler_Test (attribute interpolation asserted end-to-end).
Multiple base paths
Some apps want /views/ for the public site and /admin/views/ for the admin console.
Register two ThymeleafMixin beans, each in its own subclass with its own paths
override:
@Rest(paths={"/views/*"})
public class PublicViewsResource extends ThymeleafMixin {
public PublicViewsResource() {
super(ThymeleafMixin.create().basePath("/templates/public/"));
}
}
@Rest(paths={"/admin/views/*"})
public class AdminViewsResource extends ThymeleafMixin {
public AdminViewsResource() {
super(ThymeleafMixin.create().basePath("/templates/admin/"));
}
}
Both subclasses mount independently and each resolves templates against its own basePath.
View-mixin builder contract
ThymeleafMixin.Builder implements
ViewMixinBuilder, the
minimal contract shared by all four view-engine mixin builders (basePath(String) +
cacheTemplates(boolean)). Code written against the interface works unchanged if you later swap
engines:
import org.apache.juneau.rest.server.view.*;
ViewMixinBuilder<?> anyEngineBuilder = ThymeleafMixin.create();
anyEngineBuilder.basePath("/templates/").cacheTemplates(false);
Limitations and out-of-scope
thymeleaf-spring6is not a direct dep of the bridge module. The bridge depends only onorg.thymeleaf:thymeleaf(the engine core). Spring Boot users pullthymeleaf-spring6in transitively viaspring-boot-starter-thymeleaf; the bridge picks up the resultingSpringTemplateEnginethrough the standardBeanStorelookup.- Thymeleaf 2.x is not supported. Thymeleaf 3.x is the LTS line and ships with Spring Boot 3.x. Apps still on Thymeleaf 2.x should migrate before adopting the bridge.
- Custom dialects (Spring Security, Layout, etc.) are users' responsibility — register
them on your own
@Bean TemplateEngineand the bridge will use it instead of the default.
See also
- REST Server — Composition (mixins, paths)
- JSP View Support — sibling bridge for JSP-based apps
- Mustache View Support — sibling bridge for Mustache
- FreeMarker View Support — sibling bridge for FreeMarker
- Response Processors
- 10.0.0 release notes —
juneau-rest-server-view-thymeleaf(new module) - Thymeleaf 3 documentation