Deserialization fails with ImmutableList in Jackson 2.18.3
Jackson 2.18.3 fails to deserialize classes with @Builder (Lombok) and ImmutableList (Guava) fields. This worked in Jackson 2.18.1 but breaks after upgrading to 2.18.3 (via Spring Boot 3.4.0 → 3.4.5).
Error
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of com.google.common.collect.ImmutableList
(no Creators, like default constructor, exist)
Code that fails
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PRIVATE)
class MimirAssertsRelabelRules {
private ImmutableList<MimirRelabelRuleGroup> asserts;
}
ObjectMapper mapper = new ObjectMapper()
.registerModule(new GuavaModule());
// This throws InvalidDefinitionException
mapper.readValue(json, MimirAssertsRelabelRules.class);
Workaround that works
Add @JsonCreator to force constructor-based deserialization:
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PRIVATE)
class MimirAssertsRelabelRules {
private ImmutableList<MimirRelabelRuleGroup> asserts;
@JsonCreator
public static MimirAssertsRelabelRules fromJson(@JsonProperty("asserts") List<MimirRelabelRuleGroup> asserts) {
return new MimirAssertsRelabelRules(
asserts != null ? ImmutableList.copyOf(asserts) : ImmutableList.of()
);
}
}
This works because it bypasses the builder pattern and uses constructor-based deserialization.
Question
Can anyone help explain why this changed between 2.18.1 and 2.18.3?
The 2.18 release notes are at https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.18
Maybe, you could try 2.18.5.
Guava version change reports also has no diff between 2.18.1 and 2.18.3.
So we can basically narrow down to databind module only, BUT it's reasonable try with Jackson-ONLY reproduction @krritik if you could provide one?
Wrong repo, as per README should not file issues here. Will transfer to proper repo.
Hmmmh. So, need to de-Lombok. But unfortunately there's the Guava part so may need to actually move to
https://github.com/FasterXML/jackson-datatypes-collections/
Aside from that would second suggestion to try 2.18.5 and if that won't work, 2.19.3 or 2.20.1.
Finally, aside from seeing de-Lombokified sources, actual exception would be useful.
Thanks @cowtowncoder for moving the issue to the correct repo.
I tried 2.18.5, 2.19.3 and 2.20.1 None worked for now. I will update it with the exact error.
@JooHyukKim , let me try if I can put a more simpler Jackson-ONLY reproduction
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.google.common.collect.ImmutableList` (no Creators, like default constructor, exist): no default no-arguments constructor found
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 2, column: 5] (through reference chain: ai.asserts.prometheus.definition.rules.MimirAssertsRelabelRules["asserts"])
at app//com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at app//com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1888)
at app//com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
at app//com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1375)
at app//com.fasterxml.jackson.databind.deser.ValueInstantiator.createUsingDefault(ValueInstantiator.java:248)
at app//com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:275)
at app//com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createDefaultInstance(CollectionDeserializer.java:264)
at app//com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:246)
at app//com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
at app//com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:399)
at app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
at app//com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
at app//com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2131)
at app//com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1501)
at app//org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:397)
... 45 more
This is the error trace for now. If it is of any help.
If adding Creator works, small posibility its Guava issue.
Let's wait for de Lomboked structure and continue investigating further
Problem could be due to
at app//org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:397)
that is, depends on details of how Spring calls Jackson.
But yes, we need a stand-alone reproduction and can then figure it out.
at app//org.springframework.http.converter
This could mean Guava module not rehistered at all...
Re-reading the Creator method part , List<Pojo> is used, not directly Guava's immutablelist. I did not expect that.
@krritik Change ur creator parameter directly to Guava's ImmutableList and see what error throws
@JooHyukKim You may be looking at "work-around" case: initial one does have it:
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PRIVATE)
class MimirAssertsRelabelRules {
private ImmutableList<MimirRelabelRuleGroup> asserts;
}
same wrt GuavaModule.
Hi, sorry I could not get time. I will add a test case to reproduce it asap.
For now, I downgraded the Jackson version to 2.18.1.