DEV Community

loading...
Cover image for How to solve Jackson InvalidDefinitionException on immutable objects

How to solve Jackson InvalidDefinitionException on immutable objects

iuriimednikov profile image Yuri Mednikov Originally published at iuriimednikov.com ・2 min read

In my practice, I faced an issue, when while I was working with the Spring framework and I have an immutable object, I got InvalidDefinitionException during web layer tests. That is due to tge fact, that Jackson by default uses a no-args constructor and setters to deserialize an entity from a JSON payload. And surely, this practice leads to bad software design. So, if you also want to solve this problem, let me provide you my solution.

To start, let take a look on my entity model. Observe the following code snippet below:

@Value
@Document(collection = "customers")
public class CustomerModel {
    @Id String customerId;
    String companyName;
    String companyEmail;
    String taxId;
    AddressModel billingAddress;
    AddressModel shippingAddress;

}
Enter fullscreen mode Exit fullscreen mode

Here, you can note, that I use the @Value annotation here in order to get:

  • All arguments constructor
  • All fields are private and final
  • No setters

Using this entity class within a web layer of the application does not lead to problems, and a serialization works as expected when you call the API, for instance, with an external client. However, when I write tests to assert the web layer, I will end with an exception, because, as I have said already, Jackson can not deserialize JSON without a no-args constructor and setters:

The first solution, that I have found on Stackoverflow is to create a lombok.config file in the root of my project with following parameters:

lombok.anyConstructor.addConstructorProperties=true
Enter fullscreen mode Exit fullscreen mode

Although, this did not solve the issue. And if you are still reading this post, that means, that this solution also fails for you. What I found as a working approach is to create manually an all-args constructor and annotate it for Jackson, like it is shown in the code snippet below:

@Value
@Document(collection = "customers")
public class CustomerModel {

    @Id String customerId;
    String companyName;
    String companyEmail;
    String taxId;
    AddressModel billingAddress;
    AddressModel shippingAddress;

    @JsonCreator
    public CustomerModel(
            @JsonProperty("customerId") String customerId, 
            @JsonProperty("companyName") String companyName, 
            @JsonProperty("companyEmail") String companyEmail, 
            @JsonProperty("taxId") String taxId, 
            @JsonProperty("billingAddress") AddressModel billingAddress, 
            @JsonProperty("shippingAddress") AddressModel shippingAddress) {
        this.customerId = customerId;
        this.companyName = companyName;
        this.companyEmail = companyEmail;
        this.taxId = taxId;
        this.billingAddress = billingAddress;
        this.shippingAddress = shippingAddress;
    }

}
Enter fullscreen mode Exit fullscreen mode

Basically, I use here two annotations:

  • @JsonCreator marks a constructor to be explicitly used by Jackson for deserialization
  • @JsonProperty is used on constructor's arguments to help Jackson access concrete fields in runtime

This code perfectly works for me. By the way, don't forget to follow these steps on your nested objects too, if you have them. And I hope, it will help you too.

If you have issues with the Spring Boot that you don't know how to overcome, don't hesitate to contact me.

Discussion (2)

pic
Editor guide
Collapse
bertilmuth profile image
Bertil Muth

Hey Yuri (and all), given that you use Java 8+, you may want to check out my Moonwlker project. You will be able to get rid of the all-args constructor.

Collapse
iuriimednikov profile image
Yuri Mednikov Author

Hi Bertil, thanks for your feedback. The Moonwlker lib looks very interesting and I'll give it a try