…es calling Optional#get() unguarded
Map and Collection source properties wrapped in Optional went through
CollectionAssignmentBuilder's bare SetterWrapperForCollectionsAndMaps,
which evaluates the assignment (including the trailing Optional#get())
unconditionally, unlike scalar properties which are guarded by an
isPresent() check. An empty Optional therefore threw
NoSuchElementException instead of leaving the target property unset.
setterWrapperNeedsSourceNullCheck now requests a null-check wrapper for
Optional-typed sources, and handleLocalVarNullCheck guards the local var
assignment with isPresent() when the source is an Optional, mirroring
the existing handleSourceReferenceNullCheck behavior for scalars.
Fixes #4111
Root cause
For a source property typed Optional<T>, scalar properties are guarded with an isPresent() check before the generated code calls Optional#get() (via handleSourceReferenceNullCheck in CommonMacros.ftl). Optional<Map<K, V>> / Optional<Collection<E>> source properties don't go through that macro though — CollectionAssignmentBuilder.setterWrapperNeedsSourceNullCheck() only requested a null-check wrapper when there's a source presence checker, NullValueCheckStrategy.ALWAYS, or a DIRECT assignment, so an Optional-wrapped collection/map fell through to the bare SetterWrapperForCollectionsAndMaps, which calls the assignment (source.getAttributes().get()) unconditionally. Mapping a source whose Optional is empty throws NoSuchElementException instead of leaving the target property unset, exactly as described in the issue.
Fix
Tests
Added processor/src/test/java/org/mapstruct/ap/test/bugs/_4111/ with a mapper that has Optional<String>, Optional<Map<String, String>> and Optional<List<String>> source properties mapped to plain target properties:
Verification done: