Spring WebMvcConfigurer: Customize Default MVC Configurations

Learn about the default configurations added by Spring MVC module to a Spring Boot application as well as non-Boot application. Also, learn to use Spring WebMvcConfigurer to customize various aspects of Spring MVC, such as argument resolvers, view resolvers, exception handling, and more.

1. Introduction to Spring WebMvcConfigurer

In a Spring MVC application, generally, the WebMvcConfigurer interface is implemented by an @EnableWebMvc annotated class which defines callback methods to customize the Java-based configuration for Spring MVC. For all the unimplemented methods, the default values are used.

We can also define additional beans in this class that are related to MVC architecture of the application. This helps in keeping all the MVC-related configurations and beans in a single place.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	//overridden methods
	//additional beans
}

2. Default MVC Configurations in Spring

Before learning how to override the MVC configurations, it is good to know the default configurations provided by the Spring MVC module in an application.

2.1. In non-Spring Boot Application

In a non-spring boot application, if we do not use the @EnableWebMvc annotation, Spring MVC uses the basic settings from the DispatcherServlet. This means it will still handle incoming requests, but it won’t apply any additional configurations beyond the basics provided by the DispatcherServlet.

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>6.0.11</version>
</dependency>

For example, the DispatcherServlet supplies the following configuration:

  • The default URL pattern is ‘/*‘, thus it will handle all incoming requests.
  • The BeanNameUrlHandlerMapping is used to map URLs to beans with matching names.
  • The controller methods are invoked based on the URL mappings as normal.
  • The InternalResourceViewResolver is used to resolve the view name to an actual view template. The generated final response HTML is sent back to the client.
  • Unhandled exceptions will often result in the default error response page on the browser.
  • Note that the DispatcherServlet does not handle static resources like CSS, JavaScript, and images by default, so they may not work out of the box.

When we use the @EnableWebMvc annotation in a @Configuration class, the WebMvcConfigurationSupport class is loaded and it customizes the MVC based on which all dependencies are available on the classpath. In short, it configures Jackson, Gson and related view resolvers for XML and JSON responses, bean validation, locale resolution, CORS, exception handling etc.

By implementing WebMvcConfigurer in a @EnableWebMvc annotated class, we further customize the defaults without completely replacing them. This is especially useful when we want to keep most of the above-mentioned default configurations intact but make specific changes.

2.2. In Spring Boot Application

Spring boot uses its magic of autoconfiguration, provided by WebMvcAutoConfiguration class when we add the spring-boot-starter-web in the dependencies. At this step, Spring Boot loads the application properties and creates beans based on the specified MVC-related properties.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.1.3</version>
</dependency>

For example, by default, Spring Boot:

  • Configures a default view resolver for resolving view names to templates. It uses the InternalResourceViewResolver for JSP views and ThymeleafViewResolver for Thymeleaf templates, as found in the /resources folder.
  • Automatically configures resource handling for static content like CSS, JavaScript, and images from the ‘classpath:/static‘ location.
  • Includes support for different media types, such as JSON, XML, and HTML.
  • Provides default exception handling mechanisms.
  • Configures common libraries like Jackson for JSON serialization and deserialization.
  • Provides support for internationalization and theming by configuring message sources and themes.
  • Configure basic security settings if Spring security module is in the classpath.

Note that if we use @EnableWebMvc in a spring boot application it will disable the autoconfiguration and use configuration from WebMvcConfigurationSupport as described in the previous section. In this situation, we take over the responsibility of configuring Spring MVC, which gives us more control over the setup.

The WebMvcConfigurer interface gives additional configuration options by overriding its methods on top of @EnableWebMvc.

3. Configure View Resolvers

By overriding the viewResolver() method, we can define custom view resolvers, set up view prefixes and suffixes, and control how views are resolved.

For example, we can configure the JSP views using the following configuration:

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		registry.jsp("/WEB-INF/views/jsp/", ".jsp").cache(true);
	}
	//...
}

A similar configuration can be written for Groovy as follows:

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		registry.groovy().prefix("/WEB-INF/views/groovy/").suffix(".groovy").cache(true);
	}
	//...
}

4. Configure View Mappings

The view mappings are helpful when we do not handle the request using a @Controller handler method, rather we want to map a URL directly to the view template.

For example, we want to display the home page with almost static content or display a Contact Us page with a blank HTML form.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/").setViewName("index"); // Map root URL to "index" view
		registry.addViewController("/contact").setViewName("contact"); // Map "/contact" URL to "contact" view
	}
	// ...
}

5. Configure Static Resource Handling

Static resources such as CSS, JavaScript, images, and other static files are served using this configuration. When requests to these URLs come, Spring MVC serves the resources directly, sets the appropriate Content-Type header and automatically adds appropriate cache-control headers.

If multiple resource handlers are configured for the same URL pattern, Spring MVC will search for the resource in the order of registration. The first matching resource will be served.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/static/**")
			.addResourceLocations("/static/");
	}
	//...
}

Note that Spring MVC, by default, serves static resources from the following directories on the classpath:

  • /static
  • /public
  • /resources
  • /META-INF/resources

6. Configure Interceptors

The interceptors enable us to intercept and customize requests and responses. It is often helpful and required for tasks such as logging, authentication, authorization, and more.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {

		registry.addInterceptor(new LoggingInterceptor())
			.addPathPatterns("/api/**");
	}
	// ... 
}

Interceptors are a powerful way to add cross-cutting concerns (AOP) to the application without having to modify each controller method individually.

7. Configure Default Content Negotiation

Content negotiation allows the clients to request the same resource/response in different formats (e.g., JSON, XML, HTML) based on the client’s preferences.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		configurer.defaultContentType(MediaType.APPLICATION_JSON); // Set default content type
		configurer.favorPathExtension(true); // Enable content negotiation based on file extensions
		configurer.mediaType("xml", MediaType.APPLICATION_XML); // Define media type for XML
		configurer.mediaType("json", MediaType.APPLICATION_JSON); // Define media type for JSON
	}
	// ...
}

8. Configure Message Converters

Message converters are responsible for converting between HTTP request/response content and Java objects.

The following configuration uses MappingJackson2XmlHttpMessageConverter to convert between XML and Java objects using the Jackson XML module. And, It uses GsonHttpMessageConverter to convert between JSON and Java objects using Gson library.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		
		// Clear the default converters
		converters.clear();
		// Use Jackson for XML
		converters.add(new MappingJackson2XmlHttpMessageConverter());
		// Use Gson for JSON
		converters.add(new GsonHttpMessageConverter());
	}
	// ...
}

9. Configure CORS

Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers to restrict web pages from making requests to a different domain than the one that served the web page.

The following CORS configuration allows the cross-origin requests from origin http://localhost:8080 to URLs starting with /api/.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/api/**") 
			.allowedOrigins("http://localhost:8080") 
			.allowedMethods("GET", "POST", "PUT", "DELETE") // Allowed HTTP methods
			.allowedHeaders("*") // Allowed request headers
			.allowCredentials(false) 
			.maxAge(3600); 
	}
	// ...
}

10. Customize Exception Handling

Although the recommended approach to configure the exception handling is using the @ControllerAdvice and @ExceptionHandler annotations, we can add additonal exception handlers by configuring HandlerExceptionResolver instances.

Note that HandlerExceptionResolver operates at a lower level than @ControllerAdvice. It can be used to handle exceptions that occur outside of controllers, such as exceptions thrown during view rendering or when handling other types of requests.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
		resolvers.add(new CustomExceptionResolver());
	}

	private static class CustomExceptionResolver implements HandlerExceptionResolver {

		@Override
		public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

			if (ex instanceof SomeException) {

				ModelAndView modelAndView = new ModelAndView();
				modelAndView.setViewName("some-exception");
				modelAndView.addObject("message", "SomeException occurred: " + ex.getMessage());
				return modelAndView;
			}
			// Other exception types
		}
	}
	//...
}

11. Configure Argument Resolvers

Argument resolvers enable to customize how method parameters are resolved from the request when invoking controller methods.

For example, if we are accepting a request parameter date with some custom format and want to populate parsed the LocalDate object in the controller method argument, we can apply the following configuration:

public class CustomLocalDateArgumentResolver implements HandlerMethodArgumentResolver {

	private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.getParameterType().equals(LocalDate.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, 
		ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, 
		WebDataBinderFactory binderFactory) throws Exception {

		String dateStr = webRequest.getParameter("date"); // Get the date parameter from the request
		if (dateStr != null) {
			return LocalDate.parse(dateStr, dateFormatter); // Parse the date using the specified format
		}
		return null;
	}
}

Now register the HandlerMethodArgumentResolver with MVC configuration as follows:

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		resolvers.add(new CustomLocalDateArgumentResolver());
	}
	// ...
}

And in the controller, we can use the @RequestParam annotation with LocalDate parameters as follows:

@Controller
public class MyController {

	@GetMapping("/get-resource-by-date")
	public String processDate(@RequestParam("date") LocalDate localDate) {

		//use localDate in the code
		return "result";
	}
}

Now when we access the URL ‘http://localhost:8080/get-resource-by-date?date=2023-08-25‘, the LocalDate object in the handler method is populated with the correct value.

12. Conclusion

In conclusion, Spring WebMvcConfigurer interface allows us to customize various aspects of Spring MVC, such as argument resolvers, view resolvers, exception handling, and more. It helps in adding specific customizations while keeping the default behaviors intact.

Happy Learning !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.