Reputation: 1500
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
Reputation: 498
Spring Data REST support now only the following media types:
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