Spring MVC View Resolvers
Introduction
When building web applications with Spring MVC, one crucial component you'll encounter is the View Resolver. View resolvers are a fundamental part of the Spring MVC framework that bridge the gap between controller logic and view rendering. They determine which view should be used to render the model data that your controllers return.
In a typical Spring MVC application flow:
- The client makes a request
- A controller processes the request
- The controller returns a logical view name (a string)
- A view resolver translates this logical view name into an actual view implementation
- The view renders the response using model data
This guide will help you understand view resolvers in Spring MVC, how they work, and how to configure them for your web applications.
What is a View Resolver?
A view resolver in Spring MVC is responsible for mapping view names returned by a controller to actual view objects that will render the response. In simpler terms, it answers the question: "When my controller returns 'home', which actual view template should be used to generate the HTML?"
The ViewResolver
interface in Spring MVC has one primary method:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
Spring MVC comes with several implementations of this interface to support different view technologies like JSP, Thymeleaf, FreeMarker, and more.
Common View Resolvers in Spring MVC
Let's explore some of the most commonly used view resolvers in Spring MVC:
1. InternalResourceViewResolver
This is perhaps the most commonly used view resolver, especially for JSP views. It resolves view names to resources in a specified internal context path.
Configuration Example:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
With XML configuration:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
How it Works:
When a controller returns a view name like "home"
, the InternalResourceViewResolver
will look for a JSP file at /WEB-INF/views/home.jsp
. The prefix and suffix properties are combined with the logical view name to form the actual view path.
Controller Example:
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home"; // Will resolve to /WEB-INF/views/home.jsp
}
@GetMapping("/about")
public String about() {
return "about"; // Will resolve to /WEB-INF/views/about.jsp
}
}
2. ThymeleafViewResolver
If you're using Thymeleaf as your template engine, you'll need to configure a ThymeleafViewResolver
.
Configuration Example:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
}
3. FreeMarkerViewResolver
For FreeMarker template integration, you would use the FreeMarkerViewResolver
.
Configuration Example:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/ftl/");
return configurer;
}
@Bean
public ViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix("");
resolver.setSuffix(".ftl");
return resolver;
}
}
ContentNegotiatingViewResolver
One of the most powerful view resolvers is the ContentNegotiatingViewResolver
. Unlike other view resolvers that map a view name to a specific view implementation, this resolver delegates to other view resolvers and selects the best view based on the requested content type (e.g., HTML, JSON, XML).
Configuration Example:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver contentNegotiatingViewResolver(
ContentNegotiationManager manager,
List<ViewResolver> resolvers) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
resolver.setViewResolvers(resolvers);
// Add default views for JSON and XML
List<View> defaultViews = new ArrayList<>();
defaultViews.add(new MappingJackson2JsonView());
defaultViews.add(new MarshallingView(new Jaxb2Marshaller()));
resolver.setDefaultViews(defaultViews);
return resolver;
}
@Bean
public ViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
How it Works:
- The resolver examines the request's 'Accept' header or file extension (e.g., .html, .json)
- It determines the desired media type
- It then checks all configured view resolvers to find a suitable view
- If multiple views are available, it chooses the best match based on content type
Example Controller:
@Controller
public class UserController {
@GetMapping("/users/{id}")
public String getUserById(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "userDetails"; // Could resolve to HTML, JSON, or XML based on request
}
}
When a client requests /users/1
with an Accept: application/json
header, the user data will be returned as JSON. When requested with Accept: text/html
, the same endpoint will return an HTML page.
Setting Up Multiple View Resolvers
In real-world applications, you might need multiple view resolvers. Spring MVC allows you to define a chain of view resolvers, which are consulted in order based on their priority (order property).
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(0); // Highest priority
return resolver;
}
@Bean
public ViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(1); // Lower priority
return resolver;
}
}
In this configuration, Spring will first try to resolve views using the Thymeleaf resolver. If that fails, it will try the JSP resolver.
Practical Example: Building a Multi-Format Response API
Let's build a simple application that responds with different formats based on the client's request:
Controller:
@Controller
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/products")
public String getAllProducts(Model model) {
List<Product> products = productService.findAll();
model.addAttribute("products", products);
model.addAttribute("now", new Date());
return "products";
}
@GetMapping("/products/{id}")
public String getProductById(@PathVariable Long id, Model model) {
Product product = productService.findById(id);
model.addAttribute("product", product);
return "productDetail";
}
}
Configuration:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver contentNegotiatingViewResolver(
ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
// Set up view resolvers
List<ViewResolver> resolvers = new ArrayList<>();
resolvers.add(thymeleafViewResolver());
resolvers.add(internalResourceViewResolver());
resolver.setViewResolvers(resolvers);
// Add default views for JSON and XML
List<View> defaultViews = new ArrayList<>();
defaultViews.add(new MappingJackson2JsonView());
resolver.setDefaultViews(defaultViews);
return resolver;
}
@Bean
public ViewResolver thymeleafViewResolver() {
// Thymeleaf configuration
// ...
return resolver;
}
@Bean
public ViewResolver internalResourceViewResolver() {
// JSP configuration
// ...
return resolver;
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorPathExtension(true)
.favorParameter(true)
.parameterName("format")
.ignoreAcceptHeader(false)
.useRegisteredExtensionsOnly(false)
.defaultContentType(MediaType.TEXT_HTML)
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}
With this configuration, clients can request data in different formats:
/products
- Returns HTML by default/products.json
- Returns JSON (using path extension)/products?format=json
- Returns JSON (using query parameter)/products
withAccept: application/json
header - Returns JSON
Advanced Customization: Writing a Custom View Resolver
Sometimes you might need to create a custom view resolver for specific requirements. Here's an example of a simple custom view resolver that selects views based on a user's role:
public class RoleBasedViewResolver implements ViewResolver {
private final Map<String, ViewResolver> roleToResolverMap = new HashMap<>();
private ViewResolver defaultResolver;
public void setRoleResolvers(Map<String, ViewResolver> roleResolvers) {
this.roleToResolverMap.putAll(roleResolvers);
}
public void setDefaultResolver(ViewResolver defaultResolver) {
this.defaultResolver = defaultResolver;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
for (GrantedAuthority authority : auth.getAuthorities()) {
ViewResolver resolver = roleToResolverMap.get(authority.getAuthority());
if (resolver != null) {
View view = resolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
}
// Fall back to default resolver if no role-specific view is found
return defaultResolver != null ? defaultResolver.resolveViewName(viewName, locale) : null;
}
}
Configuration:
@Bean
public ViewResolver roleBasedViewResolver() {
RoleBasedViewResolver resolver = new RoleBasedViewResolver();
Map<String, ViewResolver> roleResolvers = new HashMap<>();
// Admin users get views from a different directory
InternalResourceViewResolver adminResolver = new InternalResourceViewResolver();
adminResolver.setPrefix("/WEB-INF/views/admin/");
adminResolver.setSuffix(".jsp");
roleResolvers.put("ROLE_ADMIN", adminResolver);
// Regular users get views from the standard directory
InternalResourceViewResolver userResolver = new InternalResourceViewResolver();
userResolver.setPrefix("/WEB-INF/views/user/");
userResolver.setSuffix(".jsp");
roleResolvers.put("ROLE_USER", userResolver);
resolver.setRoleResolvers(roleResolvers);
// Default views if no role matches
InternalResourceViewResolver defaultResolver = new InternalResourceViewResolver();
defaultResolver.setPrefix("/WEB-INF/views/");
defaultResolver.setSuffix(".jsp");
resolver.setDefaultResolver(defaultResolver);
return resolver;
}
This custom resolver will return different views depending on the user's role. For example, if an admin user accesses a page, they might see an enhanced version with additional features.
Summary
View resolvers play a critical role in Spring MVC applications by translating logical view names returned from controllers into actual view implementations. Key points to remember:
- View resolvers map logical view names to concrete view implementations
- Spring MVC provides many built-in view resolvers for different template engines
InternalResourceViewResolver
is commonly used for JSP viewsContentNegotiatingViewResolver
can serve different content types based on client requests- Multiple view resolvers can be configured with different priorities
- You can create custom view resolvers for specialized needs
By properly configuring view resolvers, you can make your Spring MVC applications more flexible, maintainable, and capable of serving different types of responses based on client requirements.
Additional Resources
Practice Exercises
- Create a Spring MVC application that uses both Thymeleaf and FreeMarker, with different view resolvers handling different sections of the website.
- Implement a ContentNegotiatingViewResolver that returns data in HTML, JSON, and PDF formats.
- Create a custom view resolver that selects different views based on the user's browser (mobile vs desktop).
- Implement a fallback mechanism that uses a series of view resolvers and falls back to a simple view if none can resolve the requested view.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)