Search code examples
javaspring-bootjacksonlombokjackson-databind

Lombok 1.18.0 and Jackson 2.9.6 not working together


The deserialization is failing after the update.

I updated my micro-service from Spring 1.5.10.RELEASE to Spring 2.0.3.RELEASE and also updated the lombok from 1.16.14 to 1.18.0 and jackson-datatype-jsr310 from 2.9.4 to 2.9.6.

The JSON string -

{"heading":"Validation failed","detail":"field must not be null"}

The Class -

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

   private final String heading;
   private final String detail;
   private String type;
}

Method call -

ErrorDetail errorDetail = asObject(jsonString, ErrorDetail.class);

The method used to deserialize -

import com.fasterxml.jackson.databind.ObjectMapper;
// more imports and class defination.

private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Error -

java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.foo.bar.ErrorDetail` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"heading":"Validation failed","detail":"field must not be null"}"; line: 1, column: 2]

Solution

  • Lombok stopped generating @ConstructorProperties on constructors with version 1.16.20 (see changelog), because it might break Java 9+ applications that use modules. That annotation contains the names of the constructor's parameters (they are removed when compiling the class, so that's a workaround so that the parameter names still can be retrieved at runtime). Because the annotation is now not being generated by default, Jackson cannot map the field names to the constructor parameters.

    Solution 1: Use a @NoArgsConstructor and @Setter, but you will loose immutability (if that's important to you).

    Update: Just @NoArgsConstructor and @Getter (without @Setter) may also work (because INFER_PROPERTY_MUTATORS=true). In this way, you can keep the class immutable, at least from regular (non-reflective) code.

    Solution 2: Configure lombok to generate the annotations again, using a lombok.config file containing the line lombok.anyConstructor.addConstructorProperties = true. (If you are using modules, make sure java.desktop is on your module path.) Clean and recompile after you added the lombok.config file.

    Solution 3: Use Jackson's builder support in combination with lombok's (@Jacksonized) @Builder/@SuperBuilder, as described in @Randakar answer to this question.

    Solution 4: When compiling with javac (of Java 8 and above), append -parameters to the command. This will store the parameter names of constructors and methods in the generated class files, so they can be retrieved via reflection.