In a Spring MVC application, a ViewResolver is responsible for mapping logical view names that your controllers return to actual .jsp files (or Thymeleaf templates, etc.). When you need to support multiple locales and provide messages or titles that vary per language, a ResourceBundleViewResolver is a convenient choice. This post walks through a minimal, but complete, configuration that you can drop into any Spring MVC project.
Problem Statement
Suppose you want a home.jsp that displays a greeting, a welcome message, and a page title, all of which should change depending on the user’s locale. You also want to keep the JSPs simple, so you delegate the translation of these messages to a standard Java ResourceBundle (properties file).
Solution Overview
- Define the view resolver so it first looks for a
messages_*.propertiesfile. - Configure the
ViewResolverhierarchy to fall back to the default view resolver if a match isn’t found. - Create a simple controller that forwards to a logical view name.
- Write the JSP that pulls values from the resource bundle.
1. Spring MVC Configuration
In modern Spring applications you typically use Java-based configuration. Add the following to your WebMvcConfig:
import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.view.ResourceBundleViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
/* ----- Locale Settings ----- */
@Bean
public LocaleResolver localeResolver() {
// Keeps locale in HTTP session; change to CookieLocaleResolver if preferred
return new SessionLocaleResolver();
}
/* ----- Message Source ----- */
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource rb = new ResourceBundleMessageSource();
rb.setBasename("messages"); // refers to messages_*.properties
rb.setDefaultEncoding("UTF-8");
return rb;
}
/* ----- View Resolvers ----- */
@Bean
public ViewResolver viewResolver() {
ResourceBundleViewResolver rbvr = new ResourceBundleViewResolver();
rbvr.setBasename("views"); // looks for views_*.properties
rbvr.setViewClass(InternalResourceViewResolver.class); // JSP suffix + prefix handled later
rbvr.setOrder(1); // higher priority (lower number = higher priority)
InternalResourceViewResolver isvr = new InternalResourceViewResolver();
isvr.setPrefix("/WEB-INF/jsp/");
isvr.setSuffix(".jsp");
isvr.setOrder(2); // fallback resolver
/* Spring MVC only picks the first capable resolver; using a
* SimpleUrlHandlerMapping ensures both are registered. */
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(Map.of("**", List.of(rbvr, isvr)));
return mapping; // return mapping instead of single resolver
}
}
What it does:
- The
ResourceBundleViewResolverreads aviews_*.propertiesfile that maps logical view names to.jspfile paths. - If that resolver cannot resolve a name, control falls back to the ordinary
InternalResourceViewResolver. - The
messageSourcebean provides internationalized strings to the JSPs via Spring’s${#messages}or the standardfmt:messagetag.
2. Service Controller
Below is a simple controller that returns the logical view name home. Spring will resolve that to the proper JSP using the resolver chain we defined.
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String showHome(Model model, HttpServletRequest request) {
// The view name "home" is resolved via ResourceBundleViewResolver
return "home";
}
}
3. Translation Resource Files
Under src/main/resources, create two sets of properties files:
# views_en.properties
home=/WEB-INF/jsp/home.jsp
# views_es.properties
home=/WEB-INF/jsp/home.jsp
The view resolver will pick the appropriate file based on the current locale. Since the file contents are identical here, you can keep them simple, but you could vary the path if different locales had distinct layouts.
For the messages to display in the view, use messages_*.properties:
# messages_en.properties
page.title=Welcome to AnkurM.com!
header.greeting=Hello
welcome.message=Thanks for visiting our site.
# messages_es.properties
page.title=¡Bienvenido a AnkurM.com!
header.greeting=Hola
welcome.message=Gracias por visitar nuestro sitio.
4. The JSP View
Create /WEB-INF/jsp/home.jsp with minimal markup that references the messages:
<!DOCTYPE html>
<html xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
<head>
<title>${page.title}</title>
</head>
<body>
<h1>${header.greeting}</h1>
<p>${welcome.message}</p>
</body>
</html>
Spring automatically adds the page alias for the messages_ bundle, so you can access the properties directly via EL.
5. Testing the Configuration
Run the application (use mvn spring-boot:run or your IDE’s Maven support). Open http://localhost:8080 and you should see the English text. To test Spanish, change the session locale (e.g., add a simple language selector or use a LocaleChangeInterceptor in the WebMvcConfigurer). After switching, the page title, greeting, and message should appear in Spanish.
Bonus: Locale Change Interceptor
For a complete user‑friendly experience, add this to WebMvcConfig:
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang"); // e.g., ?lang=es
registry.addInterceptor(lci);
}
Now navigating to /?lang=es will switch the locale to Spanish.
Wrap‑Up
By chaining ResourceBundleViewResolver with a fallback InternalResourceViewResolver, you can keep your view names concise while abstracting the actual JSP paths. The same mechanism works for other resources, such as internationalized text or static configuration files. Feel free to adapt the file names or package structure to match the conventions at ankurm.com.
Happy coding, and remember: a good resolver setup keeps both the controller logic and view templates clean and maintainable.