diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 015afbbd172..7d177667010 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -275,12 +275,11 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe "No converter found for return value of type: " + valueType); } - List compatibleMediaTypes = new ArrayList<>(); - determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes); + List compatibleMediaTypes = determineCompatibleMediaTypes(acceptableTypes, producibleTypes); // For ProblemDetail, fall back on RFC 9457 format if (compatibleMediaTypes.isEmpty() && ProblemDetail.class.isAssignableFrom(valueType)) { - determineCompatibleMediaTypes(this.problemMediaTypes, producibleTypes, compatibleMediaTypes); + compatibleMediaTypes = determineCompatibleMediaTypes(this.problemMediaTypes, producibleTypes); } if (compatibleMediaTypes.isEmpty()) { @@ -453,16 +452,18 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)); } - private void determineCompatibleMediaTypes( - List acceptableTypes, List producibleTypes, List mediaTypesToUse) { + private List determineCompatibleMediaTypes( + List acceptableTypes, List producibleTypes) { + Set compatibleTypes = new LinkedHashSet<>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { - mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); + compatibleTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } + return new ArrayList<>(compatibleTypes); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index 00f40185196..d670167c3a7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -24,6 +24,8 @@ import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -783,6 +785,24 @@ class RequestResponseBodyMethodProcessorTests { assertThat(value).isEqualTo("foo"); } + @Test // gh-36300 + void shouldNotDuplicateInCompatibleMediaTypes() throws Exception { + Method method = TestRestController.class.getMethod("handle"); + MethodParameter returnType = new MethodParameter(method, -1); + + List> converters = List.of(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter()); + RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters); + + String accept = Stream.iterate(1, i -> i + 1) + .limit(48).map(i -> "application/" + i) + .collect(Collectors.joining(",")); + accept = accept + ", application/json"; + this.servletRequest.addHeader("Accept", accept); + + processor.writeWithMessageConverters("spring framework", returnType, this.request); + } + + private void assertContentDisposition(RequestResponseBodyMethodProcessor processor, boolean expectContentDisposition, String requestURI, String comment) throws Exception {