We are facing an issue when upgrading to Spring Boot 3.2.0. We are currently using:
- Spring Boot 3.1.5
,
- Jackson 2.15.3
,
- Java 17.
openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+9-Ubuntu-122.04)
OpenJDK 64-Bit Server VM (build 17.0.9+9-Ubuntu-122.04, mixed mode, sharing)
Code is compiled using Maven with "-parameters" enabled:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<release>${java.release.version}</release>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
When upgrading from Spring Boot 3.1.x to 3.2.0, the following controller method stops working properly:
@PostMapping
@ResponseStatus(value = CREATED)
public void index(
@RequestBody final ValueWrapper<List<UserCounterValue>> wrapper) {
final Map<Class<?>, List<UserCounterValue>> map = wrapper.getValue()
.stream()
.collect(groupingBy(Object::getClass));
....
}
The ValueWrapper bean is pretty simple:
@Value
public class ValueWrapper<T> {
T value;
@JsonCreator
public ValueWrapper(
@JsonProperty("value") final T value) {
super();
this.value = requireNonNull(value);
}
}
UserCounterValue uses polymorphism with json "@type" field:
@JsonTypeInfo(use = NAME, include = PROPERTY)
public interface UserCounterValue extends UserEntity, BenchResultEntity {
}
And both concrete beans implementing this interface are declared in Jackson using mapper.registerSubTypes(...)
.
When running a junit which uses Retrofit to send the bean to the backend, it fails:
private final NumberCounterValue number = NumberCounterValueTest.newInstance();
private final TextualCounterValue text = TextualCounterValueTest.newInstance();
@Test
public void shouldIndex() {
final List<UserCounterValue> list = ImmutableList.of(text, number);
final Call<Void> index = service().index(new ValueWrapper<>(list));
calls.execute(index);
}
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.octoperf.monitor.entity.api.UserCounterValue (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.octoperf.monitor.entity.api.UserCounterValue is in unnamed module of loader 'app')
at java.base/java.util.stream.Collectors.lambda$groupingBy$53(Collectors.java:1142)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at com.octoperf.monitor.rest.server.CounterValuesController.index(CounterValuesController.java:42)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
When debugging the issue, we can see that the UserCounterValue
is not being deserialized properly:
When running the same test using Spring Boot 3.1.x:
In this case, we can see that the nested bean is deserialized properly using Jackson.
Comment From: jloisel
I narrowed down the issue to the Spring Framework dependency. It works with Spring 6.0.14
, but fails with Spring 6.1.1
.
Comment From: bclozel
I don't know if it makes any difference but the maven compiler plugin differs a bit from what we're recommending - do you still get the same issue with this configuration?
Could you share a minimal sample application that reproduces the problem? I'll transfer this issue to Spring Framework in the meantime.
Comment From: jloisel
I tried with:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>${java.release.version}</release>
<parameters>true</parameters>
</configuration>
</plugin>
Still the same issue. But, i'll keep the new syntax over the old one :)
Unfortunately, I don't have any minimal sample application. I'm checking if I can do that in a reasonable timeframe.
Comment From: jloisel
Here is a sample project which reproduces the issue:
Simply run the unit test, and it fails with a classcast exception like shown above.
Comment From: bclozel
This might be related to one of the latest binding issues we fixed here in Spring Framework. I've upgraded your sample to Spring Boot 3.2.1-SNAPSHOT and it works fine. Can you check with your actual application?
Comment From: jloisel
Glad it fixed the issue :)
I tried the snapshot maven repository but it's apparently not being able to pull spring snapshots:
<repositories>
<repository>
<id>repository.spring.snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
If you know how I can retrieve Spring Snapshots, let me know.
Comment From: bclozel
Maybe this?
<repositories>
<repository>
<id>repository.spring.snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
Artifacts are present: https://repo.spring.io/snapshot/org/springframework/spring-core/6.1.2-SNAPSHOT/
I'm closing this issue for now, we will reopen if it turns out this is not completely fixed in SNAPSHOTs.
Comment From: jloisel
Thanks!
Comment From: prabaljainn
What if i don't want to use SNAPSHOT??
Comment From: sdeleuze
@prabaljainn This discussion is from December 2023, the fix discussed here is available in GA versions for a few months already, you don't have to use snapshots.