jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Deserialization fails with ImmutableList in Jackson 2.18.3

Open krritik opened this issue 1 month ago • 11 comments

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?

krritik avatar Nov 05 '25 07:11 krritik

The 2.18 release notes are at https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.18

Maybe, you could try 2.18.5.

pjfanning avatar Nov 05 '25 07:11 pjfanning

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?

JooHyukKim avatar Nov 05 '25 12:11 JooHyukKim

Wrong repo, as per README should not file issues here. Will transfer to proper repo.

cowtowncoder avatar Nov 05 '25 18:11 cowtowncoder

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.

cowtowncoder avatar Nov 05 '25 18:11 cowtowncoder

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

krritik avatar Nov 05 '25 18:11 krritik

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.

krritik avatar Nov 05 '25 19:11 krritik

If adding Creator works, small posibility its Guava issue.

Let's wait for de Lomboked structure and continue investigating further

JooHyukKim avatar Nov 08 '25 03:11 JooHyukKim

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.

cowtowncoder avatar Nov 08 '25 04:11 cowtowncoder

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 avatar Nov 08 '25 08:11 JooHyukKim

@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.

cowtowncoder avatar Nov 09 '25 01:11 cowtowncoder

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.

krritik avatar Nov 10 '25 04:11 krritik