How to solve Jackson InvalidDefinitionException on immutable objects
Yuri Mednikov
Posted on February 5, 2021
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;
}
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
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;
}
}
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.
Posted on February 5, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.