2

We are facing StackoverflowError when we enable an except projection on a REST repository. The entity is question has two associations, a @ManyToOne with one Venue entity that has to be included inline for all responses, and a @OneToMany with Trainee entity we always want to hide. The entity (relevant) snippet

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Workshop implements Serializable {

  private static final long serialVersionUID = -5516160437873476233L;

  private Long id;

  // omitted properties

  private Venue venue;

  private Set<Trainee> trainees;

  @Id
  @GeneratedValue
  public Long getId() {
    return id;
  }

  @ManyToOne(fetch = FetchType.EAGER)
  @JoinTable(name = "workshop_venue", joinColumns = { @JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "venue_id", referencedColumnName = "id") })
  public Venue getVenue() {
    return venue;
  }

  @OneToMany
  @JoinTable(name = "workshop_trainees", joinColumns = { @JoinColumn(name = "workshiop_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "trainee_id", referencedColumnName = "email") })
  @RestResource(exported = false)
  @JsonIgnore
  public Set<Trainee> getTrainees() {
    return trainees;
  }

  // omitted getters/setters
}

when I add this Projection

@Projection(name = "default", types = { Workshop.class })
public interface InlineVenueProjection {

  String getName();

  Integer getSeatsAvailable();

  WorkshopType getWorkshopType();

  Date getDate();

  Venue getVenue();
}

and enable it in the repository

@RepositoryRestResource(collectionResourceRel = "workshop", path = "workshops", excerptProjection = InlineVenueProjection.class)
public interface WorkshopRepository extends JpaRepository<Workshop, Long> {
// omitted methods
}

I get a stack overflow error in POST requests

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.StackOverflowError

and further in the stack trace

Caused by: java.lang.StackOverflowError: null
    at java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter.initialValue(ReentrantReadWriteLock.java:286)
    at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:180)
    at java.lang.ThreadLocal.get(ThreadLocal.java:170)
    at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReleaseShared(ReentrantReadWriteLock.java:423)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(AbstractQueuedSynchronizer.java:1341)
    at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.unlock(ReentrantReadWriteLock.java:881)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:169)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:140)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:67)
    at org.springframework.data.mapping.context.PersistentEntities.getPersistentEntity(PersistentEntities.java:63)
    at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:100)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:352)

1 Answer 1

5

The reason you are getting Stack overflow exception is because I guess there is a bidirectional relationship defined between Venue (1-Many) and Workshop (Many-1). You can confirm my assumption.

When you try to serialize venue directly because of the relationship workshop is loaded and it has a reference to venue and hence the endless recursion.

Solution

To resolve this problem, Jackson has @JsonManagedReference and @JsonBackReference.

Workshop Class

public class Workshop implements Serializable {
    ...

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinTable(name = "workshop_venue", joinColumns = { @JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "venue_id", referencedColumnName = "id") })
    @JsonManagedReference
    public Venue getVenue() {
        return venue;
    }

    ...
}

Venue Class

public class Venue implements Serializable {
    ...

    @OneToMany
    @JsonBackReference
    public List<Workshop> getWorkshops() {
        return workshops;
    }

    ...
}

Hope this helps. Let me know if this works.

Sign up to request clarification or add additional context in comments.

2 Comments

is there a way to get through this without using Bi-Directional relations? I am through the StackoverflowError but now, inlining is even more complicated as it invariably shows Workshops when we fetch Venue, which is not a very good idea given our use case.
Try making the @OneToMany relationship lazy. So that when you fetch Venue.Workshops are not fetched. If that does not work, then use @JsonView to control the rendering of fields.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.