Lombok 1.18.0 and Jackson 2.9.6 not working together
Asked Answered
L

5

44

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]
Lingwood answered 22/7, 2018 at 11:40 Comment(1)
Did you try to add constructer to ErrorDetail class?Paulin
Q
86

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.

Quarrel answered 22/7, 2018 at 12:16 Comment(6)
Thank you. The first option did the trick. The second one did not work though.Lingwood
Ad 2) Did you add the lombok.config to the root folder of your project? Also note that you have to clean and recompile after doing that.Quarrel
@JanRieke - Could you please help me in this related question - #56246174Drought
NoArgsConstructor will not only lost immutability but also cannot check nullScabble
Solution 1 was not enough in my case, I had to add @AllArgsConstructor as well to get rid of another error constructor ... in class ... cannot be applied to given types. Using Java 1.8.0_265, Lombok 1.18.4, Jackson 2.9.5Symmetrical
Don't forget to add @Builder when using @Jacksonized.Homecoming
S
18

Edit: This answer is now a bit outdated: There is a new @Jacksonized annotation, from https://projectlombok.org/features/experimental/Jacksonized, which takes care of much of the boilerplate in this answer.


The best way to make jackson and lombok play well together is to always make your DTO's immutable, and tell jackson to use the builder to deserialize into your objects.

Immutable objects are good idea for the simple reason that when fields cannot be modified in situ compilers can do much more aggressive optimisations.

In order to do this you need two annotations: JsonDeserialize, and JsonPojoBuilder.

Example:

@Builder
@Value // instead of @Data
@RequiredArgsConstructor
@NonNull // Best practice, see below.
@JsonDeserialize(builder = ErrorDetail.ErrorDetailBuilder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

   private final String heading;

   // Set defaults if fields can be missing, like this:
   @Builder.Default
   private final String detail = "default detail";

   // Example of how to do optional fields, you will need to configure
   // your object mapper to support that and include the JDK 8 module in your dependencies..
   @Builder.Default
   private Optional<String> type = Optional.empty()

   @JsonPOJOBuilder(withPrefix = "")
   public static final class ErrorDetailBuilder {
   }
}
Surrounding answered 20/3, 2019 at 9:12 Comment(4)
Pffew that is a lot of boiler plate annotations for a library which intends to remove boilerplate.Carruthers
@ChristopheBouhier Most of the class-level annotation can be configured once when creating the Jackson ObjectMapper I assume they were included here for demonstration purpose ;-)Root
Actually all of this stuff has been consolidated into a single annotation: @Jacksonized projectlombok.org/features/experimental/JacksonizedSurrounding
Don't forget to add @Builder when using @Jacksonized.Homecoming
A
2

You want to deserialize a class which has final field. so u need to declare a constructor which contains final field to deserialize.

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

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

@JsonCreator
public ErrorDetail(@JsonProperty("heading") String heading, @JsonProperty("detail") String detail) {
    this.heading = heading;
    this.detail = detail;
}
}

and when deserialize with mapper need to MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS set this property false.

private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS,false).readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Actress answered 22/7, 2018 at 12:44 Comment(1)
As mentioned in the above solutions, you can use @Jacksonized (with @Builder).Homecoming
S
0

Solution 4

  • Write the NoArgsConstructor yourself. This at least worked for me with lombok 1.18.8 and Jackson 2.9.9
    @Builder
    @Getter
    @AllArgsConstructor
    public class EventDTO {

        private String id;
        private Integer isCancelled;

        private String recurringEventId;

        private String summary;
        private String description;
        private String location;
        private String startDateTime;
        private String endDateTime;

        /**
         * Make Jackson happy
         */
        public EventDTO() {
        }
    }
Senhauser answered 4/11, 2019 at 9:47 Comment(0)
T
-6

Using @Data annotation is bad approach, in my opinion. Please change @Data to @Getting , @Setter, @EqualsAndHashcode and so on ..

and write here please, if it will help.

update

I suggest, that @Data create @RequiredArgsConstructor, and it is constructor with final fields, and without private String type;

Tellurate answered 22/7, 2018 at 11:50 Comment(1)
Any reason why @Data is bad?Stile

© 2022 - 2024 — McMap. All rights reserved.