java:S6204 with SpringData/Hibernate

Template for a good false-positive report, formatted with Markdown:
*SonarQube 9.3.0.51899

  • Scanner Docker “sonarsource/sonar-scanner-cli:latest”

Trying to persist a Entity with a list (@OneToMany, @ManyToOne, @MamyToMany relationship) resultung from Lambda failed:

java.lang.UnsupportedOperationException: null
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:149)
	at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:580)
	at org.hibernate.type.CollectionType.replace(CollectionType.java:757)
	at org.hibernate.type.TypeHelper.replace(TypeHelper.java:167)
	at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:450)
	at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:205)
	at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:178)
	at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
	at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:833)
	at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:820)
	at jdk.internal.reflect.GeneratedMethodAccessor209.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
	at jdk.proxy4/jdk.proxy4.$Proxy165.merge(Unknown Source)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:633)

The reason is that spring/Hibernate try to replace the element in the list with newly persisted one.

List pass through to entities should be handled here.

Hi Robert,

I confirm that:

  • java.util.stream.Collectors#toList returns a mutable ArrayList
  • java.util.stream.Stream#toList returns a Collections.unmodifiableList(...)

So the rule S6204 does not produce false-positives on:

  • .collect(Collectors.toUnmodifiableList())

but can produce false-positives on:

  • .collect(Collectors.toList)

I see two solutions:

  1. We update the rule to only target .collect(Collectors.toUnmodifiableList())
  2. We find a heuristic to detect if the collection is really used in a mutable way

To investigate if solution 2 is feasible, could you share a code reproducer with some entity annotations and calls to .collect(Collectors.toList) ?

Alban

I will try to create a reproducer, but will require some time.