Jackson Exceptions – Problems and Solutions
1. Introduction
In this example, we shall go through the most common Jackson API exceptions encountered while working for serialization and deserialization. We will see what caused the exception to be thrown and how to fix it. Let’s dive deep.
Table Of Contents
- 1. Introduction
- 2. InvalidDefinitionException: No Creators, like default constructor, exist
- 3. MismatchedInputException: Out of START_ARRAY token
- 4. InvalidDefinitionException: No properties discovered to create BeanSerializer
- 5. InvalidFormatException: Cannot deserialize value of type `int` from String
- 6. UnrecognizedPropertyException: Unrecognized field { }
- 7. MismatchedInputException: Root name { } does not match expected
- 8. JsonParseException: Unexpected character (code 39)
- 9. Summary
- 10. Download the source code
2. “InvalidDefinitionException: No Creators, like default constructor, exist”
2.1. Model
Consider the following model class Shop for deserialization operations for this example.
Shop.java
public class Shop { public int id; public String name; public Shop(int id, String name) { this.id = id; this.name = name; } }
2.2. Exception
The below code attempts to deserialize a JSON string to an object of class Shop. The code when executed results in an exception at runtime.
Deserialization
private static void jacksonNoConstructor() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"id\":1,\"name\":\"John\"}"; try { mapper.readValue(json, Shop.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
An InvalidDefinitionException is thrown. The exception and the stack trace is:
Stack Trace
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jackson.exceptions.Shop` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: .[Source: (String undefined)"{"id":1,"name":"John"}"; line: 1, column: 2] at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from( InvalidDefinitionException.java:67 undefined)
2.3. Problem
Jackson API during deserialization is unable to identify a suitable constructor to create an instance of the class Shop, which doesn’t define a no-argument default constructor.
2.4. Solution
Provide a default constructor in the Shop class as shown below to fix this error.
Shop.java [with fix]
public class Shop { public int id; public String name; public Shop(int id, String name) { this.id = id; this.name = name; } public Shop() { } }
The deserialization after applying the fix will be successful without any exception at runtime.
3. MismatchedInputException: Out of START_ARRAY token
3.1. Model
Consider the following model class Customer with two fields id and name.
Customer.java
public class Customer { public int id; public String name; }
3.2. Exception
The below code attempts to deserialize an array of JSON objects of type Customer to an instance of the Customer class. This code when executed throws an exception at runtime.
Deserialization
private static void jacksonListToObject() { ObjectMapper mapper = new ObjectMapper(); String json = "[{\"id\":1,\"name\":\"John\"},{\"id\":2,\"name\":\"Adam\"}]"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A MismatchedInputException is thrown. The exception and the stack trace is:
Stack Trace
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `jackson.exceptions.Customer` out of START_ARRAY token at [Source: (String)"[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]"; line: 1, column: 1] at com.fasterxml.jackson.databind.exc. MismatchedInputException.from(MismatchedInputException.java:59)
3.3. Problem
We are trying to deserialize a JSON array to an object which is not of the type List but Customer. The above exception is thrown when we use the wrong type during deserialization.
3.4. Solution
The fix is to deserialize the JSON array to an object of type List<Customer> instead of type Customer. The below code demonstrates this.
Deserialization [With Fix]
private static void jacksonListToObjectFix() { ObjectMapper mapper = new ObjectMapper(); String json = "[{\"id\":1,\"name\":\"John\"}," + "{\"id\":2,\"name\":\"Adam\"}]"; try { List<Customer> customer = mapper.readValue (json, new TypeReference<List>() { }); System.out.println(customer); // Prints [Customer [id=1, name=John], // Customer [id=2, name=Adam]] } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
The deserialization after applying the fix will convert the JSON array to a List<Customer> without throwing any exception at runtime.
4. InvalidDefinitionException: No properties discovered to create BeanSerializer
4.1. Model
Consider the following model class Product.
Product.java
public class Product { int id; String name; public Product(int id, String name) { this.id = id; this.name = name; } }
4.2. Exception
The following code attempts to serialize an instance of the class Product. The code executes and errors out with an exception at runtime.
Serialization
private static void privateFieldAndGetter() { ObjectMapper mapper = new ObjectMapper(); try { Product p = new Product(1, "Anmol"); mapper.writeValueAsString(p); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
An InvalidDefinitionException is thrown. The exception and the stack trace is:
Stack Trace
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class jackson.exceptions.Product and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from (InvalidDefinitionException.java:77)
4.3. Problem
The Jackson API is unable to auto-detect public getters or public fields available in the model class for serialization. In other words, our model class does not expose any public getters or public fields.
4.4. Solution
There are multiple solutions to fix this problem. Let’s discuss each of them with examples.
4.4.1. Modify The Model Class
This is the easiest solution and we may use it only if we have an option to modify the model class and add public getters for all fields.
[Fix] Add Public Getters
public int getId() { return id; } public String getName() { return name; }
4.4.2. ObjectMapper setVisibility method
We may also choose to configure the ObjectMapper by invoking the setVisibility method. Setting the visibility of FIELD to ANY will fix this problem as shown below.
[Fix] ObjectMapper setVisibility
private static void privateFieldAndGetterFix() { ObjectMapper mapper = new ObjectMapper(); try { Product p = new Product(1, "Anmol"); mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); String json = mapper.writeValueAsString(p); System.out.println(json); // Prints {"id":1,"name":"Anmol"} } catch (JsonProcessingException e) { } }
4.4.3. @JsonAutoDetect annotation
We can also use the @JsonAutoDetect
annotation to fix this problem. For a detailed understanding of this annotation, check out the example on Jackson Annotations.
[Fix] @JsonAutoDetect
@JsonAutoDetect(fieldVisibility = Visibility.ANY) public class Product {.....}
5. InvalidFormatException: Cannot deserialize value of type `int` from String
5.1. Model
Consider the following model class Customer.
Customer.java
public class Customer { public int id; public String name; }
5.2. Exception
The below code attempts to deserialize a JSON string to an object of class Customer. The code when executed results in an exception at runtime.
Deserialization
private static void cannotDeserialize() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"id\":\"Three\",\"name\":\"Anmol\"}"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
5.3. Problem
Clearly we are trying to feed in value of an incompatible type String from the JSON to an integer field in our model class. This will not work as the text value “Three” is not a number(integer). Therefore, an InvalidFormatException is thrown.
5.4. Solution
In such cases, there are two extreme solutions.
One is to change this field’s type in the model class from int to String to make the JSON and model class compatible. The other solution is for the producer of the JSON string to serialize the value of the property in the correct type. i.e. int in this case.
6. UnrecognizedPropertyException: Unrecognized field { }
6.1. Model
Consider the following model class Customer for this example.
Customer.java
public class Customer { public int id; public String name; }
6.2. Exception
The following code attempts to deserialize a JSON string and throws an exception.
Deserialization
private static void unknownProperty() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"id\":99,\"name\":\"Anmol\",\"location\":\"Bangalore\"}"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
6.3. Problem
This is a very common exception and is thrown when an unrecognized property is identified in the JSON string which is not available in the model class.
In the above code, the JSON string contains an additional field “location” unavailable in the class Customer causing the exception to be thrown.
6.4. Solution
This problem can be solved by using multiple approaches. Let’s discuss those.
6.4.1. The @JsonIgnore annotation
We can annotate our model class with @JsonIgnoreProperties
and define a rule to ignore unknown properties. The below example shows this.
Customer.java [With Fix]
@JsonIgnoreProperties(ignoreUnknown = true) public class Customer { public int id; public String name; }
6.4.2. The DeserializationFeature
We may also use the DeserializationFeature FAIL_ON_UNKNOWN_PROPERTIES and disable it on the ObjectMapper. This will cause deserialization not to fail on detecting unknown properties.
Deserialization [With Fix]
private static void unknownPropertyFix() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"id\":99,\"name\":\"Anmol\",\"location\":\"Bangalore\"}"; mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); e.printStackTrace(); } }
The deserialization after applying the fix will be successful without any exception at runtime.
7. MismatchedInputException: Root name { } does not match expected
7.1. Model
Consider the following model class Customer for this example.
Customer.java
public class Customer { public int id; public String name; }
7.2. Exception
The below code attempts to deserialize a JSON string to an object of class Customer. The code when executed results in an exception at runtime.
Deserialization
private static void wrongJsonRoot() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"jsonRoot\" : {\"id\":1,\"name\":\"John\"}}"; try { mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A MismatchedInputException is thrown. The exception and the stack trace is:
Stack Trace
com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'jsonRoot' does not match expected ('Customer') for type [simple type, class jackson.exceptions.Customer] at [Source: (String)"{"jsonRoot" : {"id":1,"name":"John"}}"; line: 1, column: 2] (through reference chain: jackson. exceptions.Customer["jsonRoot"]) at com.fasterxml.jackson.databind.exc.MismatchedInputException. from(MismatchedInputException.java:63)
7.3. Problem
In the above code, the UNWRAP_ROOT_VAUE deserialization feature has been enabled on the ObjectMapper. This feature will unwrap the root-level JSON value while deserialization.
However, the Jackson API is unable to locate in the model class, the root name provided in the JSON string. This causes a MismatchedInputException to be thrown.
7.4. Solution
To fix this problem, we need to annotate our model class with the annotation @JsonRootName
and define the name to be used for root-level wrapping. This is shown in the example below.
Customer.java [With Fix]
@JsonRootName("jsonRoot") public class Customer { public int id; public String name; }
Deserialization After Fix
private static void wrongJsonRootFix() { ObjectMapper mapper = new ObjectMapper(); String json = "{\"jsonRoot\" : {\"id\":1,\"name\":\"John\"}}"; try { mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); Customer customer = mapper.readValue(json, Customer.class); System.out.println(customer.name + " - " + customer.id); // Print John - 1 } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
The above deserialization code will now execute without throwing any exception.
8. JsonParseException: Unexpected character (code 39)
8.1. Model
Consider the following model class Employee for this example.
Employee.java
public class Employee { public int id; public String name; }
8.2. Exception
The following code attempts to deserialize a JSON string to an instance of class Employee and errors out with an exception.
Deserialization
private static void code39Exception() { ObjectMapper mapper = new ObjectMapper(); String json = "{'id':99,'name':'Anmol'}"; try { mapper.readValue(json, Employee.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A JsonParseException is thrown. The exception and stack trace is:
Stack Trace
com.fasterxml.jackson.core.JsonParseException: Unexpected character (''' (code 39)): was expecting double-quote to start field name at [Source: (String)"{'id':99,'name':'Anmol'}"; line: 1, column: 3] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1851)
8.3. Problem
If we carefully observe the deserialization code in the section above, the JSON string is composed of single-quotes instead of double-quotes. Therefore, deserializing a JSON string with single-quotes results in a code 39 error.
8.4. Solution
The ObjectMapper class provides a constructor to which we can supply our own JsonFactory with some features enabled. The fix for this problem is also around the same lines. We can configure the ObjectMapper to allow single quotes as demonstrated in the below example.
[Fix] A JsonFactory With Single Quotes enabled, supplied to ObjectMapper
private static void code39Fix() { String json = "{'id':99,'name':'Anmol'}"; try { JsonFactory jf = new JsonFactory(); jf.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); ObjectMapper mapper = new ObjectMapper(jf); Employee employee = mapper.readValue(json, Employee.class); System.out.println(employee.id + " = " + employee.name); // Prints 99 = Anmol } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
9. Summary
In this article, we spoke about Jackson Exceptions. Specifically, we explored some common exceptions that are encountered in programming with the Jackson API for serialization/deserialization. We did a deep dive into the causes of such exceptions and looked at the way to fix them.
10. Download the source code
You can download the full source code of this example here: Jackson Exceptions – Problems and Solutions