Spring Boot Auto-configuration & Properties
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Thymeleaf 3.1.5, Spring Boot 4.1.1 (Spring Framework 7.0.9), Java 21 and the Layout Dialect
4.0.1, all checked on 2026-10-07. It applies to Thymeleaf with Spring MVC and with Spring WebFlux: the auto-configuration
builds a servlet or a reactive view resolver depending on the type of web application, and the sections below say which
property or bean belongs to which stack. It does not apply to standalone Thymeleaf, which has no auto-configuration (see
the Standalone Engine & Configuration page). Boot 4 moved Thymeleaf support out of the old
single spring-boot-autoconfigure jar into its own spring-boot-thymeleaf module, so code and blog posts written for
Boot 3 mention package names and test annotations that moved; where that matters the text says so.
In the Bookshelf application that grows across this section, almost everything you never write in Java is done here: the
resolver that turns the view name books/list into classpath:/templates/books/list.html, the engine that is shared by
every request, the layout and Spring Security dialects and the view resolver that renders the page. This page opens the
box, lists every switch Boot gives you, shows how to add an SVG icon resolver and a custom dialect, how to replace the view
resolver, how to test it all, and what DevTools changes during development.
In a hurry: what goes where
| Need | Do this | Section |
|---|---|---|
Thymeleaf in a Boot web application |
Add |
|
Know what exists in the context |
One |
|
Change a default |
Set a |
|
A second template location (icons, mail, text) |
Declare an additional |
|
A custom dialect |
Declare it as a bean; every |
|
Replace the view resolver |
Declare a bean named |
|
Test it |
|
|
Template edits not showing up |
DevTools sets |
The starters
Boot 4 splits the integration into three artifacts, all managed by the Spring Boot BOM so that none of them needs a version number in a Boot project:
Artifact (org.springframework.boot) |
What it is |
|---|---|
|
The application starter. It depends on |
|
The module that holds |
|
The test starter. It depends on |
Neither the starter nor the module brings a web stack; a Thymeleaf-only application is not a web application, so the
view resolvers below are only created when spring-boot-starter-webmvc or spring-boot-starter-webflux is present.
The Layout Dialect, the Spring Security dialect and the data-attribute dialect are optional: Boot registers each one only
if its jar is on the classpath.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- optional dialects: versions are managed by the Boot BOM -->
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
ThymeleafAutoConfiguration: what it creates
org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfiguration is registered through the
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file of the spring-boot-thymeleaf
module. It is a @AutoConfiguration ordered after the WebMvc and WebFlux auto-configurations, active when
TemplateMode and SpringTemplateEngine are on the classpath, and it binds ThymeleafProperties to the
spring.thymeleaf prefix. Everything it declares is a plain @Bean with a back-off condition, so you replace a piece by
declaring your own, not by editing configuration.
| Bean (name) | Created when | Notes |
|---|---|---|
|
No bean named |
Built from |
|
No |
Sets the SpringEL compiler and hidden-checkbox-marker flags from properties, then adds every |
|
The application is reactive and no |
The same assembly as above, for WebFlux. |
|
Servlet application, |
Character encoding and |
|
Reactive application and no bean named |
Media types, chunk size, full-mode and chunked-mode view names from |
|
Servlet application, a resource chain is enabled and no other filter bean of that type exists. |
Registers |
|
|
Enables |
|
|
Enables the |
|
|
Enables the |
The engine is assembled from collections, not from fixed beans. Because SpringTemplateEngine receives
ObjectProvider<ITemplateResolver> and ObjectProvider<IDialect>, any resolver or dialect bean in the context is picked
up, including those that come from third-party starters; the htmx integration htmx-spring-boot-thymeleaf works this
way by declaring an IDialect bean (named htmxDialect in the scratch application). The SpringStandardDialect is the
engine’s default dialect and is always present.
The next test prints what the engine really contains in the Bookshelf application (Layout Dialect, Spring Security and the htmx integration are on its classpath, plus the two extras added later on this page):
@SpringBootTest
class AutoConfigurationTest {
@Autowired ApplicationContext ctx;
@Autowired SpringTemplateEngine engine;
@Test
void listsBeans() {
System.out.println(Arrays.toString(ctx.getBeanNamesForType(ITemplateResolver.class)));
System.out.println(engine.getDialects().stream().map(IDialect::getName).toList());
System.out.println(engine.getTemplateResolvers().stream()
.map(r -> r.getName() + ":" + r.getOrder()).toList());
}
}
[iconTemplateResolver, defaultTemplateResolver]
[SpringStandard, Bookshelf, Htmx, SpringSecurity, Layout]
[org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver:2, org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver:null]
The spring.thymeleaf properties
All properties live under the spring.thymeleaf prefix and are bound to ThymeleafProperties. The defaults below were
read from the spring-configuration-metadata.json of spring-boot-thymeleaf 4.1.1 and from its source. The
reactive-only and servlet-only keys are marked.
| Property | Default | Effect |
|---|---|---|
|
|
Whether the template resolver caches parsed templates and whether the servlet view resolver caches its views. DevTools turns it off (see DevTools defaults). |
|
|
Sets |
|
|
At startup, warns if the prefix location does not exist. It never fails the application. |
|
|
Turns on the SpringEL compiler for expressions in templates, a performance option for hot templates. |
|
|
Character encoding of the template files and of the response (servlet view resolver) or the default charset (reactive). |
|
none |
View names, patterns allowed, that the Thymeleaf view resolver must not resolve. |
|
|
The |
|
|
Prepended to the view name to build the resource location. |
|
|
Appended to the view name. |
|
none |
Order of the default resolver in the chain. Needed only if you declare additional resolver beans (see Extra template resolvers: an SVG icon resolver). |
|
none |
View names, patterns allowed, that can be resolved; everything else is left to other view resolvers. |
|
|
Renders the hidden marker input of a checkbox before the checkbox itself instead of after it. |
|
|
The |
|
|
Start writing output as early as possible; set it to |
|
none |
Views run in CHUNKED mode when a maximum chunk size is set; if this list is given, only these. |
|
none |
Views run in FULL mode even when a maximum chunk size is set. |
|
|
Maximum size of the data buffers written to the response; only values above zero are applied, and templates then run in CHUNKED mode by default. |
|
|
The media types the reactive view resolver supports. |
|
removed |
Deprecated with level |
Three things the table does not show. First, check-template and check-template-location look alike but do different
jobs: the first is a per-view check with an effect on resolver chaining, the second a one-time startup warning. Second,
the servlet-only keys are not honoured by a WebFlux application; the reactive keys do nothing in an MVC one. Third, the
properties configure only the default resolver and the view resolver Boot creates: a resolver or view resolver you declare
yourself ignores them.
# application.properties -- Bookshelf production defaults, written out explicitly
spring.thymeleaf.cache=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
# application-local.properties -- loaded with --spring.profiles.active=local
spring.thymeleaf.cache=false
A test that sets several of them shows how they map onto the beans (output from the scratch application):
@SpringBootTest(properties = {
"spring.thymeleaf.cache=true",
"spring.thymeleaf.template-resolver-order=1",
"spring.thymeleaf.view-names=books/*,fragments/*",
"spring.thymeleaf.excluded-view-names=books/icons",
"spring.thymeleaf.servlet.content-type=application/xhtml+xml",
"spring.thymeleaf.servlet.produce-partial-output-while-processing=false"})
class PropertiesEffectTest { /* reads thymeleafViewResolver and defaultTemplateResolver */ }
application/xhtml+xml;charset=UTF-8 cache=true partial=false viewNames=[books/*, fragments/*] excluded=[books/icons]
| resolver order=1 cacheable=true mode=HTML checkExistence=true enc=UTF-8
For a WebFlux application the same mapping happens on ThymeleafReactiveViewResolver: with
spring.thymeleaf.reactive.max-chunk-size=8KB and spring.thymeleaf.reactive.full-mode-view-names=hello the scratch
reactive application reported a response maximum chunk size of 8192 bytes and a full-mode view list of hello.
Extra template resolvers: an SVG icon resolver
The default resolver reads HTML from classpath:/templates/. Anything else, such as inline SVG icons, plain-text mail
bodies or XML, needs a second resolver with its own prefix, suffix and template mode. Declare it as a bean: the engine
collects every ITemplateResolver bean in the context. Three settings make a second resolver safe:
-
resolvablePatternsrestricts it to the template names it owns (icons/*), so it never swallows view names. -
checkExistencemakes it decline a missing resource, so the chain moves on instead of failing. -
orderfixes who is asked first. Resolvers are consulted in ascending order, and in the scratch application a resolver with no order was asked after one with order 2. Becausespring.thymeleaf.template-resolver-orderis unset by default, set it (or the order of yours) explicitly as soon as you add a second resolver.
@Configuration(proxyBeanMethods = false)
class ThymeleafExtrasConfig {
@Bean
SpringResourceTemplateResolver iconTemplateResolver(ApplicationContext context) {
var resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(context);
resolver.setPrefix("classpath:/");
resolver.setSuffix(".svg");
resolver.setResolvablePatterns(Set.of("icons/*"));
resolver.setTemplateMode(TemplateMode.XML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCheckExistence(true);
resolver.setOrder(2);
return resolver;
}
}
The SVG file is src/main/resources/icons/book.svg, outside templates/ and static/. Note the prefix: the template name
is icons/book, so the prefix is classpath:/, not classpath:/icons/. With the longer prefix Thymeleaf looks for
icons/icons/book.svg and fails with Error resolving template [icons/book], template might not exist or might not be
accessible by any of the configured Template Resolvers.
<p><span th:replace="~{icons/book}"></span> <span th:text="${#isbn.format('9780134685991')}">isbn</span></p>
Rendered by the scratch application (the second part comes from the dialect of the next section):
<p><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path d="M4 4h12a4 4 0 0 1 4 4v12H8a4 4 0 0 1-4-4z" fill="none" stroke="currentColor" stroke-width="2"/>
</svg>
<span>978-0134685991</span></p>
Extra dialect beans
A dialect is registered the same way: declare a bean. Every IDialect bean is added to the engine in @Order order, so a
dialect in your own configuration, in a library or from a starter needs no wiring. The following minimal dialect adds a
#isbn expression object, which is the lightest kind of extension (the full extension mechanisms, processors and
dialects, are the subject of the Extending Thymeleaf page):
@Bean
BookshelfDialect bookshelfDialect() {
return new BookshelfDialect();
}
static class BookshelfDialect extends AbstractDialect implements IExpressionObjectDialect {
BookshelfDialect() {
super("Bookshelf");
}
@Override
public IExpressionObjectFactory getExpressionObjectFactory() {
return new IExpressionObjectFactory() {
@Override public Set<String> getAllExpressionObjectNames() { return Set.of("isbn"); }
@Override public Object buildObject(IExpressionContext context, String name) { return new Isbn(); }
@Override public boolean isCacheable(String name) { return true; }
};
}
}
public static class Isbn {
public String format(String digits) {
return digits.length() == 13 ? digits.substring(0, 3) + "-" + digits.substring(3) : digits;
}
}
The three dialects Boot registers by itself follow the same rule, with a twist: each one is @ConditionalOnMissingBean, so
declaring your own LayoutDialect bean (for example one built with non-default options) replaces the default one rather
than adding a second.
Source: Extending Thymeleaf
Overriding the view resolver
The view resolver backs off on a bean name, not on a bean type. That makes replacing it simple and also explains the first pitfall of this page: a resolver declared under a different name does not replace the auto-configured one, it joins it.
For Spring MVC declare a bean named thymeleafViewResolver. The test below replaces the default one with a resolver that
only handles books/* and runs first; afterwards the context holds exactly one ThymeleafViewResolver, with order 1.
@SpringBootTest
@Import(ViewResolverOverrideTest.CustomViewResolver.class)
class ViewResolverOverrideTest {
@TestConfiguration(proxyBeanMethods = false)
static class CustomViewResolver {
@Bean
ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine engine) {
var resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(engine);
resolver.setCharacterEncoding("UTF-8");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setViewNames(new String[] {"books/*"});
resolver.setOrder(1);
return resolver;
}
}
@Autowired ApplicationContext ctx;
@Test
void customResolverWins() {
var resolvers = ctx.getBeansOfType(ThymeleafViewResolver.class);
assertThat(resolvers).hasSize(1);
assertThat(resolvers.get("thymeleafViewResolver").getOrder()).isEqualTo(1);
}
}
For WebFlux the auto-configured bean is named thymeleafViewResolver but its back-off condition is on the name
thymeleafReactiveViewResolver. In the scratch reactive application the default bean was listed as
[thymeleafViewResolver], and declaring a ThymeleafReactiveViewResolver bean named thymeleafReactiveViewResolver
left a single resolver, yours:
@Bean
ThymeleafReactiveViewResolver thymeleafReactiveViewResolver(ISpringWebFluxTemplateEngine engine) {
var resolver = new ThymeleafReactiveViewResolver();
resolver.setTemplateEngine(engine);
resolver.setOrder(1);
return resolver;
}
Most of the reasons to override the resolver have a property: view-names and excluded-view-names to share the
application with another view technology, servlet.content-type for XHTML, produce-partial-output-while-processing to
buffer, the reactive.* keys for chunking. Declare the bean only when a property does not exist. A resolver you declare
no longer reads those properties; you set everything on it yourself.
Versioned static resources and the ResourceUrlEncodingFilter
@{/css/site.css} becomes a content-hashed URL only when two things hold: a Spring resource chain with a version strategy
is enabled, and ResourceUrlEncodingFilter wraps the response so that encodeURL can rewrite links. Boot registers that
filter for you when a chain is enabled; without one, no filter bean exists, which is exactly what the scratch application
showed with default settings. With the chain enabled:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
<link rel="stylesheet" th:href="@{/css/site.css}">
<link rel="stylesheet" href="/css/site-3c7253c0b17971959285c18f6eea6509.css">
The filter bean resourceUrlEncodingFilter appeared in the context only after the properties were added. DevTools sets
spring.web.resources.chain.cache=false (see below) but does not enable the chain. The link syntax itself is covered on
the Link URLs page.
Testing: spring-boot-starter-thymeleaf-test
spring-boot-starter-thymeleaf-test brings the Thymeleaf starter and spring-boot-starter-test, and the
spring-boot-thymeleaf module contributes to the test slices. Its META-INF/spring descriptors list ThymeleafAutoConfiguration for
the @AutoConfigureWebMvc and @AutoConfigureWebFlux imports, and list org.thymeleaf.dialect.IDialect in the include
filters of @WebMvcTest and @WebFluxTest. In practice:
-
a
@WebMvcTestrenders real templates through the real engine;MockMvcreturns the HTML. -
an
IDialectbean that is itself a scanned component is included in the slice. A dialect or a resolver declared in a@Configurationclass is not: slices exclude@Configurationclasses you do not name, so the icon resolver above needs an explicit@Import. -
in Boot 4
@WebMvcTestlives inorg.springframework.boot.webmvc.test.autoconfigureand@AutoConfigureMockMvcin the same package; the oldorg.springframework.boot.test.autoconfigure.web.servletimports from Boot 3 no longer compile.
@WebMvcTest(IconController.class)
@Import(ThymeleafExtrasConfig.class)
@WithMockUser
class IconWebMvcTest {
@Autowired MockMvc mvc;
@Test
void rendersIconAndFormatsIsbn() throws Exception {
mvc.perform(get("/icons-demo"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("<svg")))
.andExpect(content().string(containsString("978-0134685991")));
}
}
Removing the @Import line makes the same test fail at render time with Error resolving template [icons/book], because
the slice then has no icon resolver. The test starter does not add Spring Security’s test support; the example uses
spring-boot-starter-security-test because the Bookshelf application has Spring Security.
DevTools defaults
spring-boot-devtools applies a list of development-time property defaults. For Thymeleaf the entry that matters is
spring.thymeleaf.cache=false: since Boot 4 each module contributes its own default through a
META-INF/spring-devtools.properties file (spring-boot-thymeleaf ships defaults.spring.thymeleaf.cache=false). The
same list also switches off the caches of the other template engines and the static resource chain, sets
spring.web.resources.cache.period=0, enables the H2 console and makes error responses include the message and stack
trace. A property you set yourself always wins.
The Bookshelf application with DevTools on the classpath and no spring.thymeleaf.cache anywhere, then with the defaults
disabled (values printed by a throwaway runner from the scratch application):
./mvnw spring-boot:run
# cache=false viewResolverCache=false h2=true chain=false
./mvnw spring-boot:run -Dspring-boot.run.arguments="--spring.devtools.add-properties=false"
# cache=null viewResolverCache=true h2=null chain=null
Three consequences follow. DevTools is not on the classpath of a packaged jar or in tests, so production keeps
cache=true and a @SpringBootTest ignores the DevTools default; if you do not use DevTools, set the property in a
local profile as in the properties example above. Only changes that DevTools can see reach the browser, so a template
edit in src/main/resources/templates needs a rebuild of the classpath resources (next section). And LiveReload is
deprecated as of Spring Boot 4.1.0 with no replacement: the embedded LiveReload server still exists and is controlled by
spring.devtools.livereload.enabled, but do not build a workflow on it; changes under /templates, /static and
/public do not trigger a restart by default (they trigger a live reload), so a template edit reaches the next request through the disabled cache, not through a restart.
IDE classpath pitfalls
A template that is found by ./mvnw spring-boot:run or a packaged jar but not when the application starts from the IDE
is almost always a classpath ordering or staleness problem, not a Thymeleaf one. The Boot reference states that running
from the IDE’s main method orders the classpath differently from Maven, Gradle or a packaged jar, which can make Boot fail
to find the expected template, and recommends reordering the classpath so that the module’s own classes and resources come
first. Related traps:
-
target/classes(orbuild/resources/main) is what the application reads, notsrc/main/resources. If the IDE does not copy changed resources there, an edited template looks unchanged even withcache=false. Build the project (IntelliJ IDEA: Build Project; Eclipse and Spring Tools: save) so that the classpath changes and DevTools reacts. -
The DevTools restart works by watching classpath directories and needs the build tool’s fork enabled: a Maven or Gradle run that disables forking has no restart class loader.
-
A default prefix of
classpath:/templates/finds templates in every jar on the classpath. Two modules that both containtemplates/index.htmlshadow each other according to classpath order. -
On a case-insensitive file system (macOS, Windows)
books/Listrenders locally and fails in a Linux container. View names are paths: match the case. -
The startup warning
Cannot find template location: classpath:/templates/means the directory is missing from the classpath, not that a template is wrong; silence it withspring.thymeleaf.check-template-location=falseonly for a deliberately template-less module.
Worked example: Bookshelf configuration end to end
The pieces above compose into the configuration of the Bookshelf application: one profile switches the cache, one configuration class adds the icon resolver and the ISBN dialect, and one test proves the result.
# src/main/resources/application.properties
spring.application.name=bookshelf
spring.thymeleaf.template-resolver-order=1
# src/main/resources/application-local.properties
spring.thymeleaf.cache=false
<!-- templates/books/list.html (excerpt) -->
<li th:each="b : ${books}">
<span th:replace="~{icons/book}"></span>
<span th:text="${b.title}">Title</span>
<small th:text="${#isbn.format(b.isbn)}">978-0-13468599-1</small>
</li>
With template-resolver-order=1 the default resolver is asked first for every name; for icons/book it finds no
templates/icons/book.html (check-template=true makes it decline), so the icon resolver with resolvablePatterns takes
over. The cost of the extra resolver is one failed resource lookup per uncached template, which the template cache removes
in production. The profile is activated with --spring.profiles.active=local. A @SpringBootTest that saved a book with ISBN
9780134685991 and requested /books rendered the SVG followed by 978-0134685991 for each row, on Spring Boot 4.1.1.
Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
A second |
The bean has a name other than |
Name the bean exactly as the condition expects, or use the properties. |
|
The extra resolver’s prefix repeats the directory that is already part of the template name. |
Prefix |
The extra resolver never gets used, or it hides regular views. |
No |
Set an order on both resolvers and restrict the new one with |
|
The property is removed (error-level deprecation since 4.0.0). |
Depend on |
|
The dialect jar is not on the classpath; Boot registers each dialect only when its jar is present. |
Add the dependency; check |
Static resources are not versioned. |
|
Set |
Template edits are not visible. |
Caching is on, DevTools is absent, or the IDE did not update the classpath. |
Use a |
|
The slice does not scan |
|
|
Each group configures only its own view resolver. |
Use the keys of the stack in use (see the properties table). |
Boot 3 imports such as |
Boot 4 moved the test annotations next to their modules. |
Use |
Your own resolver ignores |
Properties configure only the beans Boot creates. |
Set the value on your bean, or drop the bean. |