developer10
developer10

Reputation: 1500

Infinite recursion occurs on a JSON retrieval of a basic class even though there are no relationships to other entities

I have this simple class (Spring Boot + JPA/Hibernate) that is being used just for testing.

@Entity
@Table
public class User {

    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    // getters + setters
    ....
}

Since by default Spring RestMvc seems to be returning ContentType:application/hal+json, and for my front-end client app in Ember.js I need application/vnd.api+json, I did the following change:

spring.data.rest.defaultMediaType=application/vnd.api+json

Now after I make a request from the client app, I get matching ContentType for both request and response.

But.. Now when I try to directly or via Postman access the API: localhost:8080/api/users/1, the app enters into an infinite loop and a stackoverflow occurs after some time.

I tried some workarounds using Jackson's annotations, like @JsonIgnoreProperties, etc. but that didn't help.

What confuses me most is the fact this class isn't related to any other classes/entities so what could be be causing this loop?

EDIT:

2017-11-04 18:03:35.594  INFO 17468 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-11-04 18:03:35.607  INFO 17468 --- [           main] c.i.restapp.RestAppApplication           : Started RestAppApplication in 27.08 seconds (JVM running for 66.023)
2017-11-04 18:04:08.780  INFO 17468 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-11-04 18:04:08.781  INFO 17468 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2017-11-04 18:04:08.882  INFO 17468 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 101 ms
Hibernate: select user0_.id as id1_1_0_, user0_.first_name as first_na2_1_0_, user0_.last_name as last_nam3_1_0_ from user user0_ where user0_.id=?
2017-11-04 18:04:54.726  WARN 17468 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain:
org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping
.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping
.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping

Upvotes: 1

Views: 642

Answers (1)

sergey
sergey

Reputation: 498

Spring Data REST support now only the following media types:

  • application/hal+json
  • application/json

https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.item-resource

but you can add application/vnd.api+json in supported media types for HAL JacksonHttpMessageConverter.

You can customize the message converters by extending the WebMvcConfigurerAdapter class and overriding the extendMessageConverters method:

package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    private static final MediaType APPLICATION_VND_API_JSON = MediaType.valueOf("application/vnd.api+json");
    private static final String HAL_JSON_SUBTYPE = "hal+json";

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.stream()
                .filter(TypeConstrainedMappingJackson2HttpMessageConverter.class::isInstance)
                .map(TypeConstrainedMappingJackson2HttpMessageConverter.class::cast)
                .filter(this::isHalConverter)
                .forEach(this::addVndApiMediaType);
        super.extendMessageConverters(converters);
    }

    private boolean isHalConverter(TypeConstrainedMappingJackson2HttpMessageConverter converter) {
        return converter.getSupportedMediaTypes().stream().anyMatch(type -> type.getSubtype().equals(HAL_JSON_SUBTYPE));
    }

    private void addVndApiMediaType(TypeConstrainedMappingJackson2HttpMessageConverter converter) {
        List<MediaType> supportedMediaTypes = new ArrayList<>(converter.getSupportedMediaTypes());
        supportedMediaTypes.add(APPLICATION_VND_API_JSON);
        converter.setSupportedMediaTypes(supportedMediaTypes);
    }
}

It still requires the parameter in application.properties:

spring.data.rest.defaultMediaType=application/vnd.api+json

Unfortunately requests with application/hal+json will not work after this dirty hack.

Upvotes: 1

Related Questions