Java Infinite recursion (StackOverflowError) Jackson solutions

The problem seems to be the reations between Objects, in my example i'll use Customer and Order , a customer has a list of orders, and the order contains a reference back to the Customer.

public class Customer {
    private Long id;
    private String name;
    private List<Order> orders;
.....
public class Order {
    private Long id;
    private Customer customer;

this is a common situation :)

            String orderAsString =  objectMapper.writeValueAsString(customer);

this line will generate a StackOverflowException 

com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: ...)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:706)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
.....

For this problem I like two solutions, the other ones needs extra work and will lose the ability to transform the JSON back to java Objects.

1. @JsonManagedReference and @JsonBackReference

 this one works wery well, it will exclude back references from output but will keep the relation between them, when we will recreate the objects we will have the entore structure and all references in place

public class Customer {
    private Long id;
    private String name;
    @JsonManagedReference
    private List<Order> orders;

public class Order {
    private Long id;
    @JsonBackReference
    private Customer customer;

the output :

{
  "id" : 1,
  "name" : "The Cusotmer",
  "orders" : [ {
    "id" : 1
  }, {
    "id" : 2
  } ]
}

as you can see the customer is not included in orders.

the code :

            String orderAsString =  objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
            System.out.println(orderAsString);
            Customer customerFromJson = objectMapper.readValue(orderAsString, Customer.class);
 

when we are transforming the JSON to Java objects we will have customers in place:

Jackson JsonManagedReference JsonBackReference Example

2. JsonIdentityInfo 

Whist this one you have to take care about scope and also you need to have an id on your POJO otherwise will be hard to implement it correct.

@JsonIdentityInfo( scope = Customer.class,
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id"
)
public class Customer {
    private Long id;
.....
@JsonIdentityInfo(
        scope = Order.class,
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id"
)
public class Order {
    private Long id;

the code used for this one is the same 

            String orderAsString =  objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
            System.out.println(orderAsString);
            Customer customerFromJson = objectMapper.readValue(orderAsString, Customer.class);

and the JSON is like on solution 1, also the the Objects created from JSON .

JsonIdentityInfo Objects example

Note , not using scope will throw :

com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.Long) [[ObjectId: key=1, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]]

Other examples , use them only if you are forced to do this

 forced 1 : StdSerializer

        final ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Order.class, new StdSerializer<Order>(Order.class) {
            @Override
            public void serialize(Order value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeNumberField("id", value.getId());
                jsonGenerator.writeEndObject();
            }
        });
        objectMapper.registerModule(module);
        try {
            String orderAsString =  objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
            System.out.println(orderAsString);
            Customer customerFromJson = objectMapper.readValue(orderAsString, Customer.class);
            System.out.println(customerFromJson);

you will have dirty hands after writing this kind of solution :), and the orders will contains a null customer !

forced 2 : FilterProvider

@JsonFilter("filterOrder")
public class Order {
    private Long id;
    private Customer customer;
...

and the code :

        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        String[] ordrerIgnorableFieldNames = { "customer" };
        FilterProvider filters = new SimpleFilterProvider()
                .addFilter("filterOrder", SimpleBeanPropertyFilter.serializeAllExcept(ordrerIgnorableFieldNames));
        ObjectWriter writer = objectMapper.writer(filters);
        try {
            String orderAsString =  writer.writeValueAsString(customer);
            System.out.println(orderAsString);
            Customer customerFromJson = objectMapper.readValue(orderAsString, Customer.class);
            System.out.println(customerFromJson);

again, the orders will contains null customer.

download sources from GitHub Sources

Category: Java Tutorials