Collections no longer copied if marked non-null
@hduelme 已经在做这个了。
开始于 2026年8月27日。
评估
这个 Issue 还没有评估数据。
描述
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
- 主要语言
- Java
- 星标
- 7.7k
- 派生
- 1.1k
- PR 合并指标
- 30 天内没有已合并 PR
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
mapstruct/mapstruct 的其他 Issue
-
bug test
难度 4/5 3-5 天 新手友好度 48/100
-
bug
难度 3/5 1-2 天 新手友好度 68/100
-
难度 4/5 3-5 天 新手友好度 68/100
-
难度 3/5 1-2 天 新手友好度 72/100
-
JSpecify
查看 mapstruct/mapstruct 的全部 Issue
相似的 Issue
-
area/plugin
难度 2/5 1-3 小时 新手友好度 75/100
kestra-io/plugin-kestra#190 ·
-
难度 2/5 1-3 小时 新手友好度 70/100
google-ai-edge/LiteRT-LM#3739 ·
-
bug
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 75/100
integra-team-red/meet-map#249 ·
-
[Studio][Bug] Cancelled create-user dialog keeps the password and admin switch for the next attempt 未关闭
难度 2/5 1-3 小时 新手友好度 75/100
apache/rocketmq-dashboard#5064 ·