Last Modified 5.15.2025
Using Thymeleaf in WebFlux
https://github.com/kimjonghoon/webflux-thymeleaf
Run
mvn jetty:run
Visit http://localhost:8080
POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.java_school.tutorial</groupId> <artifactId>webflux-thymeleaf</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>WebFlux Thymeleaf</name> <url>http://localhost:8080</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <spring.version>6.2.6</spring.version> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-framework-bom</artifactId> <version>${spring.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring6 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring6</artifactId> <version>3.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.1.0-alpha1</version> </dependency> <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.5.18</version> <scope>compile</scope> </dependency> </dependencies> <build> <finalName>webflux-thymeleaf</finalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.4.1</version> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.3</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>3.1.4</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>3.1.4</version> </plugin> <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-maven-plugin --> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>11.0.25</version> </plugin> </plugins> </pluginManagement> </build> </project>
Java-based Configuration
package net.java_school.config; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.ViewResolverRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine; import org.thymeleaf.spring6.SpringWebFluxTemplateEngine; import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver; import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver; import org.thymeleaf.templatemode.TemplateMode; @Configuration @EnableWebFlux @ComponentScan("net.java_school.blog") public class AppConfig implements ApplicationContextAware, WebFluxConfigurer { @Autowired private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:static/"); } @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } @Bean public SpringResourceTemplateResolver templateResolver() { final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); resolver.setApplicationContext(this.applicationContext); resolver.setPrefix("classpath:templates/"); resolver.setSuffix(".html"); resolver.setTemplateMode(TemplateMode.HTML); resolver.setCacheable(false); return resolver; } @Bean public ISpringWebFluxTemplateEngine templateEngine() { SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); templateEngine.setEnableSpringELCompiler(true); return templateEngine; } @Bean public ThymeleafReactiveViewResolver viewResolver() { ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver(); viewResolver.setTemplateEngine(templateEngine()); viewResolver.setResponseMaxChunkSizeBytes(16348); return viewResolver; } @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(this.viewResolver()); } }
package net.java_school.config; import org.springframework.web.server.adapter.AbstractReactiveWebInitializer; public class MyWebApplicationInitializer extends AbstractReactiveWebInitializer { @Override protected Class<?>[] getConfigClasses() { return new Class<?>[] {AppConfig.class}; } }
Controller
package net.java_school.blog; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Controller public class MyController { @GetMapping("/") public String index() { return "index"; } @GetMapping(value={"{chapter:java|jdbc|jsp|css-layout|jsp-pjt|spring|javascript|google-cloud}", "{chapter:java|jdbc|jsp|css-layout|jsp-pjt|spring|javascript|google-cloud}/{lecture}"}) public String lecture(@PathVariable("chapter") String chapter, @PathVariable(value="lecture", required=false) String lecture) { if (lecture != null) { return chapter + "/" + lecture; } else { return chapter + "/index"; } } @GetMapping("blog") public String getBlogHome() { return "blog/index"; } @GetMapping("blog/{year:\\d+}/{post}") public String getPost(@PathVariable("year") year, @PathVariable("post") post) { return "blog/" + year + "/" + post; } }References