Optional<Map> / Optional<Collection> source property generates unguarded Optional#get, throwing NoSuchElementException when empty
まだ誰も着手していません。
評価
調査の方向性
示されている javac コマンドを使用して Source.java、Target.java、SourceMapper.java、Demo.java で問題を再現し、その後、生成された SourceMapperImpl.java を調べる。保護されたスカラー処理と照らし合わせて、map と collection の処理を追跡する。Optional と Optional の空の値が NoSuchElementException なしでマッピングされ、関連するリグレッションカバレッジが通れば完了とする。
索引モデルが issue の本文から書いたものです。
説明
Expected behavior
When a source property is Optional<T>, the generated mapper should read the value only when it is present — as it already does for scalar types:
if ( source.getName().isPresent() ) {
name = source.getName().get();
}
Actual behavior
For Optional<Map<K, V>> and Optional<List<E>> source properties the presence check is omitted and Optional#get is called unconditionally, so mapping a source whose Optional is empty throws NoSuchElementException.
Generated mapper (note the scalar property in the same method is guarded):
@Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
String name = null;
Map<String, String> attributes = null;
List<String> tags = null;
if ( source.getName().isPresent() ) {
name = source.getName().get();
}
attributes = source.getAttributes().get(); // no presence check
tags = source.getTags().get(); // no presence check
Target target = new Target( name, attributes, tags );
return target;
}
Runtime, mapping a Source whose three properties are all Optional.empty():
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at repro.SourceMapperImpl.toTarget(SourceMapperImpl.java:27)
at Demo.main(Demo.java:9)
The inconsistency between the scalar and the map/collection properties in the same generated method suggests the presence check is lost on the branch that handles map and collection types.
This makes Optional-returning getters unusable in practice for map and collection properties, since an absent value is exactly the case Optional exists to express.
Related to #3976, but that one concerns get() vs orElseThrow(); here the presence check is missing altogether.
Steps to reproduce the problem
Source.java:
package repro;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class Source
{
private final Optional<String> name;
private final Optional<Map<String, String>> attributes;
private final Optional<List<String>> tags;
public Source( Optional<String> name, Optional<Map<String, String>> attributes, Optional<List<String>> tags )
{
this.name = name;
this.attributes = attributes;
this.tags = tags;
}
public Optional<String> getName()
{
return name;
}
public Optional<Map<String, String>> getAttributes()
{
return attributes;
}
public Optional<List<String>> getTags()
{
return tags;
}
}
Target.java:
package repro;
import java.util.List;
import java.util.Map;
public record Target( String name, Map<String, String> attributes, List<String> tags )
{ }
SourceMapper.java:
package repro;
import org.mapstruct.Mapper;
@Mapper
public interface SourceMapper
{
Target toTarget( Source source );
}
Demo.java:
import java.util.Optional;
import repro.*;
public class Demo
{
public static void main( String[] args )
{
Source source = new Source( Optional.empty(), Optional.empty(), Optional.empty() );
System.out.println( new SourceMapperImpl().toTarget( source ) );
}
}
Compiled with plain javac (no build tool, no other annotation processors):
javac -cp mapstruct-1.7.0.Beta2.jar \
-processorpath mapstruct-processor-1.7.0.Beta2.jar:mapstruct-1.7.0.Beta2.jar \
-s gen -d out src/repro/*.java
Also reproduces with nullValueMapMappingStrategy = RETURN_NULL and with nullValueCheckStrategy = ALWAYS.
MapStruct Version
1.7.0.Beta2, Java 25.0.4 (Eclipse Adoptium), javac.
- 主要言語
- Java
- スター
- 7.7k
- フォーク
- 1.1k
- PR マージ指標
- 30日以内にマージされた PR はありません
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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
-
bug JSpecify
-
JSpecify
mapstruct/mapstruct の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
elastic/gradle-plugins#157 ·
-
enhancement Tools
難易度 1/5 1時間未満 初心者へのやさしさ 75/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
apache/rocketmq-dashboard#5008 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
DETECT_PARAMETER_NAMES=false silently disables @ConstructorProperties-based Creator detection too オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
FasterXML/jackson-databind#6229 ·