Using multiple ViewResolvers in Spring Boot

View more categories:

1- Objective of Post

Normally, in a Spring application, you usually need to use only one technology for the View layer, which can be a Thymeleaf, JSP or FreeMarker, etc. However, you can also use multiple technologies for the  View layer at the same time. In this post, I am going to guide you for creating a such application.
OK, We will create an application that uses all of the 3  technologies such as Thymeleaf, JSP & FreeMarker for the View layer.

2- Create a Spring Boot project

3- Configure pom.xml

To be able to use the  JSP, Thymeleaf, FreeMarker, you need to have the following libraries:
<!-- Thymeleaf -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- FreeMarker -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Web -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSP -->
<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- JSTL -->
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
</dependency>
The full content of the  pom.xml file:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>org.o7planning</groupId>
    <artifactId>SpringBootMultiViewResolver</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringBootMultiViewResolver</name>
    <description>Spring Boot + Multi ViewResolver</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        
        <!-- Thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <!-- FreeMarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>        
        
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- JSP -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <!-- JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>


</project>

4- Configure ViewResolvers

When developing a Spring Boot project, you usually use only one technology for the View layer ( JSP, Thymeleaf, ..), the Spring Boot will automatically configure a ViewResolver for you to work with that technology. However, in case you use multiple technologies for the View layer, you have to manually configure all necessary ViewResolvers.
This is an illustration of the flow of a Spring application if you use a ViewResolver.
In case, you use multiple technologies for the View layer, a lot of ViewResolvers  take part in the flow of the application. The ViewResolvers are arranged in order of priority (0, 1, 2, ..). If ViewResolver (0) does not find the required "View Name" , the ViewResolver (1) will be used, ... 

Configure Thymeleaf ViewResolver.

ThymeleafViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
public class ThymeleafViewResolverConfig {

    @Bean
    public ViewResolver thymeleafViewResolver() {

        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();

        viewResolver.setTemplateEngine(thymeleafTemplateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setOrder(0);

        // Important!!
        // th_page1.html, th_page2.html, ...
        viewResolver.setViewNames(new String[] { "th_*" });

        return viewResolver;
    }

    // Thymeleaf template engine with Spring integration
    @Bean
    public SpringTemplateEngine thymeleafTemplateEngine() {

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(thymeleafTemplateResolver());
        templateEngine.setEnableSpringELCompiler(true);

        return templateEngine;
    }

    @Bean
    public SpringResourceTemplateResolver springResourceTemplateResolver() {
        return new SpringResourceTemplateResolver();
    }

    // Thymeleaf template resolver serving HTML 5
    @Bean
    public ITemplateResolver thymeleafTemplateResolver() {

        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();

        templateResolver.setPrefix("templates/");
        templateResolver.setCacheable(false);
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCharacterEncoding("UTF-8");

        return templateResolver;
    }
 
}
In this application, we configure the  Thymeleaf ViewResolver with the highest priority (order = 0).
Note: The Thymeleaf ViewResolver will throw an exception when it does not find the required " View Name" (A necessary html file). It is contrary to your wish that the ViewResolver with succeeding priority will be used. Therefore, you need to set up rules for the " View Names" to be served by the Thymeleaf ViewResolver.
// Important!!
// th_page1.html, th_page2.html, ...
viewResolver.setViewNames(new String[] { "th_*" });

Configure FreeMarker ViewResolver.

FreeMarkerViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

@Configuration
public class FreeMarkerViewResolverConfig {

    @Bean(name = "viewResolver")
    public ViewResolver getViewResolver() {
        FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();

        viewResolver.setCache(true);
        viewResolver.setPrefix("/freemarker/");
        viewResolver.setSuffix(".ftl");
        viewResolver.setOrder(1);
        return viewResolver;
    }

    @Bean(name = "freemarkerConfig")
    public FreeMarkerConfigurer getFreemarkerConfig() {
        FreeMarkerConfigurer config = new FreeMarkerConfigurer();

        // Folder containing FreeMarker templates.
        // 1 - "/WEB-INF/views/"
        // 2 - "classpath:/templates"
        config.setTemplateLoaderPath("classpath:/templates");
        return config;
    }
    
}
The operating principle of the  FreeMarker ViewResolver:

Configure JSP ViewResolver.

Note: The  JSP ViewResolver has to be established with the lowest priority. This is commented in the doucments of  Spring.
JspViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
public class JspViewResolverConfig {

    @Bean
    public ViewResolver jspViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setContentType("text/html");
        
        // Make sure > Thymeleaf order & FreeMarker order.
        viewResolver.setOrder(1000);
        
        return viewResolver;
    }
    
}
The operating principles of the JSP ViewResolver:

5- Controller

MainController.java
package org.o7planning.sbmultiviewresolver.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MainController {

    @RequestMapping(value = { "/testJsp" }, method = RequestMethod.GET)
    public String testJspView() {

        return "testJsp";
    }

    @RequestMapping(value = { "/testThymeleaf" }, method = RequestMethod.GET)
    public String testThymeleafView() {

        return "th_page1";
    }
    
    @RequestMapping(value = { "/testFreeMarker" }, method = RequestMethod.GET)
    public String testFreeMarkerView() {

        return "testFreeMarker";
    }
    
}

6- Views

th_page1.html (Thymeleaf)
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>Thymeleaf</title>
   </head>
   <body>
   
      <h2>Thymeleaf Page</h2>
      <p>templates/th_page1.html</p>
      
   </body>
</html>
freemaker/testFreeMarker.ftl (FreeMarker)
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>FreeMarker</title>
   </head>
   <body>
   
      <h2>FreeMarker Page</h2>
      <p>templates/freemarker/testFreeMarker.ftl</p>
      
   </body>
</html>
testJsp.jsp
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>JSP</title>
   </head>
   <body>
   
      <h2>JSP Page</h2>
      <p>WEB-INF/jsp/testJsp.jsp</p>
      
   </body>
</html>

View more categories: