How to solve Jackson InvalidDefinitionException on immutable objects

iuriimednikov

Yuri Mednikov

Posted on February 5, 2021

How to solve Jackson InvalidDefinitionException on immutable objects

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.

💖 💪 🙅 🚩
iuriimednikov
Yuri Mednikov

Posted on February 5, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related