diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index dd647965521a..11d4b7ccb88c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -76,6 +76,7 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.AntPathMatcher; import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -271,8 +272,12 @@ public void configureContentNegotiation(ContentNegotiationConfigurer configurer) if (contentnegotiation.getParameterName() != null) { configurer.parameterName(contentnegotiation.getParameterName()); } - Map mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes(); + Map mediaTypes = contentnegotiation.getMediaTypes(); mediaTypes.forEach(configurer::mediaType); + List defaultContentTypes = contentnegotiation.getDefaultContentTypes(); + if (!CollectionUtils.isEmpty(defaultContentTypes)) { + configurer.defaultContentType(defaultContentTypes.toArray(new MediaType[0])); + } } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java index 6d4b76e647a5..3459190ef6ea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java @@ -17,7 +17,9 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.time.Duration; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -317,6 +319,12 @@ public static class Contentnegotiation { */ private String parameterName; + /** + * The default content types to be used when no specific content type is + * requested. + */ + private List defaultContentTypes = new ArrayList<>(); + public boolean isFavorParameter() { return this.favorParameter; } @@ -341,6 +349,14 @@ public void setParameterName(String parameterName) { this.parameterName = parameterName; } + public List getDefaultContentTypes() { + return this.defaultContentTypes; + } + + public void setDefaultContentTypes(List defaultContentTypes) { + this.defaultContentTypes = defaultContentTypes; + } + } public static class Pathmatch { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 474c049e3532..d4eb2ddd23fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,7 @@ import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; @@ -86,6 +87,7 @@ import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.accept.FixedContentNegotiationStrategy; import org.springframework.web.accept.ParameterContentNegotiationStrategy; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; @@ -577,6 +579,22 @@ void customMediaTypes() { }); } + @Test + void defaultContentTypes() { + this.contextRunner + .withPropertyValues("spring.mvc.contentnegotiation.default-content-types:application/json,application/xml") + .run((context) -> { + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); + ContentNegotiationManager contentNegotiationManager = (ContentNegotiationManager) ReflectionTestUtils + .getField(adapter, "contentNegotiationManager"); + assertThat(contentNegotiationManager).isNotNull(); + assertThat(contentNegotiationManager.getStrategy(FixedContentNegotiationStrategy.class)) + .extracting(FixedContentNegotiationStrategy::getContentTypes) + .asInstanceOf(InstanceOfAssertFactories.list(MediaType.class)) + .containsExactly(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML); + }); + } + @Test void formContentFilterIsAutoConfigured() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(OrderedFormContentFilter.class));