Spring MVC configuration and wiring conventions for NUS Java applications. Covers DispatcherServlet setup, MVC configuration (@EnableWebMvc, WebMvcConfigurer), view resolvers, message converters, handler mappings, interceptors, CORS, and static resource serving. Applicable to both standalone Spring MVC applications (no Spring Boot) and Spring Boot applications that use Spring MVC. Use this skill when reviewing or implementing Spring MVC infrastructure configuration.
Scope: Spring MVC infrastructure configuration —
DispatcherServlet,@EnableWebMvc,WebMvcConfigurer, message converters, view resolvers, interceptors, CORS, static resources, and multipart configuration. Not for API contract rules or controller mechanics.Applicability:
- ✅ Spring MVC-only applications (no Spring Boot; web.xml or programmatic servlet init).
- ✅ Spring Boot + MVC applications (Boot auto-configures MVC;
WebMvcConfigurerbeans extend the defaults without breaking Boot's auto-configuration; see Section B).See also:
- Coding skill (boundary-safe errors + sensitive data + review checklist):
.github/skills/coding/SKILL.md- Java umbrella skill (Java idioms + defaults):
.github/skills/java/SKILL.md- Java REST API skill (endpoint style, DTOs, response envelopes, error mapping):
.github/skills/java/rest-api/SKILL.md- Java Spring Controller skill (binding, multipart, advice mechanics, slice testing):
.github/skills/java/spring-controller/SKILL.md.github/skills/java/spring-service/SKILL.md.github/skills/java/spring-security/SKILL.md.github/skills/java/jpa/SKILL.md.github/skills/java/jdbc/SKILL.md.github/skills/java/spring-boot/SKILL.mdFor an existing application, you MUST follow the repo's established patterns for:
web.xml vs AbstractAnnotationConfigDispatcherServletInitializer
vs Spring Boot embedded container).@EnableWebMvc is present (it disables Boot auto-configuration; see B.2).WebMvcConfigurer beans — their structure and what they customize.You MUST NOT introduce @EnableWebMvc in a Spring Boot application unless you intend to
fully own all MVC configuration (see B.2). You MUST NOT refactor the MVC wiring architecture
(e.g., moving from web.xml to Java init, or from Boot auto-config to manual config) unless
explicitly requested.
In a plain Spring MVC application (WAR deployed to a container), the DispatcherServlet and
WebApplicationContext must be configured explicitly.
Programmatic initializer (recommended for new MVC-only apps):
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class }; // services, persistence
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class }; // controllers, MVC config
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
MVC configuration class (plain Spring MVC-only):
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "nus.example.web")
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
converters.add(converter);
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
return mapper;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestLoggingInterceptor());
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// see Section E for CORS rules
}
}
In a Spring Boot application, MVC is auto-configured. Key rules:
@Configuration classes with @EnableWebMvc unless you intend to
fully replace Boot's auto-configuration. Doing so silently disables many Boot defaults
(Jackson auto-configuration, spring.mvc.* property binding, etc.).WebMvcConfigurer in a
@Configuration bean without @EnableWebMvc.WebMvcConfigurer hooks or Spring Boot properties (spring.mvc.*, spring.jackson.*).// GOOD: extends Boot's auto-config without replacing it
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestLoggingInterceptor());
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// see Section E for CORS rules
}
}
// BAD: @EnableWebMvc in a Boot app disables auto-configuration
@Configuration
@EnableWebMvc // <-- removes Boot's Jackson/MVC defaults
public class WebConfig implements WebMvcConfigurer { ... }
application.properties / application.yml
(prefixed spring.jackson.*) or by declaring an ObjectMapper bean. Do not call
configureMessageConverters unless replacing all converters.MappingJackson2HttpMessageConverter in configureMessageConverters.Common Jackson defaults (align with repo):
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
application/json for REST/JSON APIs.configureContentNegotiation unless there is a specific requirement (e.g., XML
support alongside JSON).HandlerInterceptor directly (do not extend HandlerInterceptorAdapter; it was removed in Spring 5.3).WebMvcConfigurer#addInterceptors.public class RequestLoggingInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// log opaque request ID only — no URI params, no body, no auth headers
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
log.info("request: method={} uri={} requestId={}",
request.getMethod(), request.getRequestURI(), requestId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
MDC.clear();
}
}
Rules:
WebMvcConfigurer#addCorsMappings (or Spring Boot's
spring.mvc.cors.* if simple enough).@CrossOrigin on individual controllers for org-wide CORS policy; prefer
centralized configuration.* (wildcard) in production
configurations that include credentials.@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://app.example.nus.edu.sg")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(3600);
}
WebMvcConfigurer#addResourceHandlers.src/main/resources/static is auto-served; do not duplicate the mapping
unless customizing cache control or path prefix.@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS));
}
For applications that render server-side views (Thymeleaf, JSP, FreeMarker):
Example (Spring MVC-only + Thymeleaf):
@Bean
public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
For file upload in Spring MVC-only apps, configure MultipartResolver:
// Spring MVC-only: CommonsMultipartResolver (commons-fileupload on classpath)
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(10 * 1024 * 1024); // 10 MB
return resolver;
}
For Spring Boot, multipart is auto-configured via spring.servlet.multipart.* properties.
MUST NOT declare a CommonsMultipartResolver bean in Boot unless explicitly required and
Boot's auto-configuration is intentionally disabled.
When reviewing or generating Spring MVC configuration code, verify:
@EnableWebMvc is absent in Spring Boot applications (unless fully replacing Boot auto-config)WebMvcConfigurer is used (not WebMvcConfigurerAdapter, which is removed in Spring 5.3+)* with credentials in productionspring.servlet.multipart.*; Spring MVC-only uses MultipartResolver bean@CrossOrigin on individual controllers for org-wide policy