Collections no longer copied if marked non-null
@hduelme ci sta già lavorando.
Dal 27/8/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Expected behavior
When copying a non-null (JSpecify) collection property not only the null-check is skipped (correctly) but also the copy of the collection is not performed. This means that if the source object had an immutable collection, now so does the destination object. Previously, the destination was always mutable.
I'm not sure if this is an intended change of behaviour or a defect. If it's intended, it contradicts section 5.2 of the documentation which says:
If source and target attribute have the same type, the value will be simply copied direct from source to target. If the attribute is a collection (e.g. a List) a copy of the collection will be set into the target attribute.
Actual behavior
Assuming we have a class Bar:
@NullMarked
class Bar {
private List<Foo> foos;
...
In previous versions, a List copy (where source and destination are both Bars) would use this code:
List<Foo> list = source.getFoos();
if ( list != null ) {
destination.setFoos( new ArrayList<Foo>( list ) );
}
However, if getFoos() is marked as non-null, in 1.7.0.Beta2 we now get:
destination.setFoos( source.getFoos() );
This causes trouble for us because we actually use two MapStruct-generated methods as follows:
Bar copy(Bar source); // this used to leave `foos` mutable on the copied result
Bar patch(BarForm form, @MappingTarget Bar target); // this uses `clear()` and `addAll()` to modify `foos`
The workaround is either to add an explicit mapping to copy to force the collection-copying as per the docs:
@Mapping(target = "foos", expression = "java(new ArrayList<>(source.getFoos()))")
or to change the collection behaviour for the entire mapper using collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE to stop it trying to clear() an immutable collection.
Steps to reproduce the problem
package mapstruct;
import java.util.List;
import org.jspecify.annotations.NullMarked;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
public class BarMapperTest {
public static void main(String[] args) {
Bar bar = new Bar(); // immutable foos
BarMapper mapper = Mappers.getMapper(BarMapper.class);
Bar patchTarget = mapper.copy(bar);
BarForm form = new BarForm(List.of(new Foo()));
mapper.patch(form, patchTarget);
System.out.println("Patched foos size: " + patchTarget.getFoos().size()); // Should print 1
}
}
@Mapper
interface BarMapper {
Bar copy(Bar source);
Bar patch(BarForm form, @MappingTarget Bar target);
}
@NullMarked
class Bar {
List<Foo> foos = List.of();
public List<Foo> getFoos() {
return foos;
}
public void setFoos(List<Foo> foos) {
this.foos = foos;
}
}
class Foo {}
class BarForm {
List<Foo> foos;
public BarForm(List<Foo> foos) {
this.foos = foos;
}
public List<Foo> getFoos() {
return foos;
}
}
This blows up with:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:159)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:166)
at mapstruct.BarMapperImpl.patch(BarMapperImpl.java:38)
at mapstruct.BarMapperTest.main(BarMapperTest.java:18)
In version 1.6.3 it completes successfully, printing Patched foos size: 1
MapStruct Version
1.7.0.Beta2
- Lingua principale
- Java
- Stelle
- 7.7k
- Fork
- 1.1k
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di mapstruct/mapstruct
-
bug test
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
-
bug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 68/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 72/100
-
JSpecify
Tutte le issue di mapstruct/mapstruct
Issue simili
-
executions.Query — startDate and timeRange filters are sent with inverted comparison operators Apertaarea/plugin
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
kestra-io/plugin-kestra#190 ·
-
litertlm-android AAR ships no consumer ProGuard rules → "mid == null" SIGABRT in minified apps Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
google-ai-edge/LiteRT-LM#3739 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
integra-team-red/meet-map#249 ·
-
[Studio][Bug] Cancelled create-user dialog keeps the password and admin switch for the next attempt Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
apache/rocketmq-dashboard#5064 ·