Skip to content

Commit a439e90

Browse files
committed
Fix collection support in AbstractKotlinSerializationHttpMessageConverter
AbstractKotlinSerializationHttpMessageConverter#getSupportedMediaTypes(Class<?>) currently invokes transitively supports(Class<?>) which always return false with generic types. This commit adds an override that just invokes getSupportedMediaTypes(). Closes gh-34992
1 parent aa5c0dc commit a439e90

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Type;
2222
import java.util.HashSet;
23+
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
2526

@@ -76,6 +77,11 @@ protected AbstractKotlinSerializationHttpMessageConverter(T format, MediaType...
7677
this.format = format;
7778
}
7879

80+
@Override
81+
public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
82+
return getSupportedMediaTypes();
83+
}
84+
7985
@Override
8086
protected boolean supports(Class<?> clazz) {
8187
return serializer(ResolvableType.forClass(clazz)) != null;

spring-webmvc/spring-webmvc.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
description = "Spring Web MVC"
22

33
apply plugin: "kotlin"
4+
apply plugin: "kotlinx-serialization"
45

56
dependencies {
67
api(project(":spring-aop"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.mvc.method.annotation
18+
19+
import kotlinx.serialization.Serializable
20+
import org.assertj.core.api.Assertions
21+
import org.junit.jupiter.api.Test
22+
import org.springframework.http.converter.StringHttpMessageConverter
23+
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
24+
import org.springframework.web.bind.annotation.RequestMapping
25+
import org.springframework.web.bind.annotation.ResponseBody
26+
import org.springframework.web.context.request.NativeWebRequest
27+
import org.springframework.web.context.request.ServletWebRequest
28+
import org.springframework.web.method.HandlerMethod
29+
import org.springframework.web.method.support.ModelAndViewContainer
30+
import org.springframework.web.testfixture.servlet.MockHttpServletRequest
31+
import org.springframework.web.testfixture.servlet.MockHttpServletResponse
32+
import kotlin.reflect.jvm.javaMethod
33+
34+
/**
35+
* Kotlin tests for [RequestResponseBodyMethodProcessor].
36+
*/
37+
class RequestResponseBodyMethodProcessorKotlinTests {
38+
39+
private val container = ModelAndViewContainer()
40+
41+
private val servletRequest = MockHttpServletRequest()
42+
43+
private val servletResponse = MockHttpServletResponse()
44+
45+
private val request: NativeWebRequest = ServletWebRequest(servletRequest, servletResponse)
46+
47+
@Test
48+
fun writeWithKotlinSerializationJsonMessageConverter() {
49+
val method = SampleController::writeMessage::javaMethod.get()!!
50+
val handlerMethod = HandlerMethod(SampleController(), method)
51+
val methodReturnType = handlerMethod.returnType
52+
53+
val converters = listOf(KotlinSerializationJsonHttpMessageConverter())
54+
val processor = RequestResponseBodyMethodProcessor(converters, null, null)
55+
56+
val returnValue: Any = SampleController().writeMessage()
57+
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
58+
59+
Assertions.assertThat(this.servletResponse.contentAsString)
60+
.contains("\"value\":\"foo\"")
61+
}
62+
63+
@Test
64+
fun writeGenericTypeWithKotlinSerializationJsonMessageConverter() {
65+
val method = SampleController::writeMessages::javaMethod.get()!!
66+
val handlerMethod = HandlerMethod(SampleController(), method)
67+
val methodReturnType = handlerMethod.returnType
68+
69+
val converters = listOf(KotlinSerializationJsonHttpMessageConverter())
70+
val processor = RequestResponseBodyMethodProcessor(converters, null, null)
71+
72+
val returnValue: Any = SampleController().writeMessages()
73+
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
74+
75+
Assertions.assertThat(this.servletResponse.contentAsString)
76+
.contains("\"value\":\"foo\"")
77+
.contains("\"value\":\"bar\"")
78+
}
79+
80+
@Test
81+
fun readWithKotlinSerializationJsonMessageConverter() {
82+
val method = SampleController::readMessage::javaMethod.get()!!
83+
val handlerMethod = HandlerMethod(SampleController(), method)
84+
val methodReturnType = handlerMethod.returnType
85+
86+
val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter())
87+
val processor = RequestResponseBodyMethodProcessor(converters, null, null)
88+
89+
val returnValue: Any = SampleController().readMessage(Message("foo"))
90+
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
91+
92+
Assertions.assertThat(this.servletResponse.contentAsString).isEqualTo("foo")
93+
}
94+
95+
@Test
96+
fun readGenericTypeWithKotlinSerializationJsonMessageConverter() {
97+
val method = SampleController::readMessages::javaMethod.get()!!
98+
val handlerMethod = HandlerMethod(SampleController(), method)
99+
val methodReturnType = handlerMethod.returnType
100+
101+
val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter())
102+
val processor = RequestResponseBodyMethodProcessor(converters, null, null)
103+
104+
val returnValue: Any = SampleController().readMessages(listOf(Message("foo"), Message("bar")))
105+
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
106+
107+
Assertions.assertThat(this.servletResponse.contentAsString)
108+
.isEqualTo("foo bar")
109+
}
110+
111+
112+
private class SampleController {
113+
114+
@RequestMapping
115+
@ResponseBody
116+
fun writeMessage() = Message("foo")
117+
118+
@RequestMapping
119+
@ResponseBody
120+
fun writeMessages() = listOf(Message("foo"), Message("bar"))
121+
122+
@RequestMapping
123+
@ResponseBody
124+
fun readMessage(message: Message) = message.value
125+
126+
@RequestMapping
127+
@ResponseBody fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }
128+
129+
}
130+
131+
@Serializable
132+
data class Message(val value: String)
133+
}

0 commit comments

Comments
 (0)