Files
spring-boot-demo/mapstruct/README.md
T

5.0 KiB

mapstruct

Companion project for the article MapStruct with Spring Boot 4 and Java Records: DTO Mapping Done Right on ankurm.com.

Every console block quoted in the article came out of output/. Transcripts 01-16 are written by the test suite (so a claim that stops being true turns the build red); 17-24 are the compiler's own words, captured by scripts/compile-errors.sh from the deliberately broken sources in src/broken/; 25 is read out of the processor jar by scripts/capture-facts.sh.

There is deliberately no docs/ folder: the deeper material lives in collapsible "going deeper" sections inside the article itself.

Versions

Spring Boot 4.1.2 (newest GA in Maven Central's metadata when this was written; the sibling modules use 4.1.1)
Spring Framework 7.0.10
MapStruct 1.6.3 (newest GA; 1.7.0.Beta2 exists and is not used)
Jackson 3.1.7 (tools.jackson)
Dozer (test scope, timing only) 7.0.0
JDK 25
Maven 3.9

No Lombok. Records make it unnecessary here, and scripts/compile-errors.sh shows what Lombok does to the processor path when you do use it (transcript 24).

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn test                  # runs the scenarios and rewrites output/01-16
./scripts/run-all.sh      # everything, including 17-25 (needs the jars from one `mvn test` in ~/.m2)

The compiler plugin gets the generator through annotationProcessorPaths (see pom.xml). The generated sources land in target/generated-sources/annotations/, where the tests read them back to print them.

Source layout

Path What it holds
domain/ mutable entity-style classes: Customer, Address, Order, OrderLine
dto/ records: CustomerDto, CustomerRequest, CustomerPatch, AddressDto, OrderDto, ReceiptDto (with a builder), ...
mapper/ CentralConfig (Spring beans, constructor injection, unmapped target = error) and the mappers
web/ a small controller and an in-memory repository, so the records can be seen over HTTP
src/test/.../probe/ throwaway mappers that answer "what happens if...": hook order, @ObjectFactory, cycles, timing
src/broken/ sources that must not compile; never part of the Maven build

Captured output

File What it shows
01-records-as-source-and-target.txt a record target has no setters; the generated code calls the constructor
02-generated-implementation.txt the generated AddressMapperImpl and CustomerMapperImpl.toDto, read from target/
03-entity-to-record.txt nested objects, List/Set/Map, expression, constant, defaultValue, qualifiedByName, @Context
04-record-to-entity.txt a record as source, @AfterMapping, @InheritInverseConfiguration, nullValueCheckStrategy
05-null-strategies.txt IGNORE versus the default on a patch; RETURN_DEFAULT for whole objects and collections
06-update-in-place.txt @MappingTarget updates mutate nested objects and collections, including immutable ones
07-hooks-on-records.txt @BeforeMapping / @AfterMapping call order for a record target and a bean target
08-object-factory-trap.txt an @ObjectFactory that returns a record: nothing is copied, no error
09-builder-and-decorator.txt post-processing a record through its builder, and through @DecoratedWith
10-context-and-cycles.txt @Context cycle tracking works for beans and cannot work for records
11-testing-mappers-plain.txt Mappers.getMapper versus constructing the generated class
12-testing-mappers-contextconfiguration.txt a Spring context of just the generated classes
13-testing-mappers-springboottest.txt the same mapper inside @SpringBootTest
14-property-level-test-strategy.txt one mutation per record component, and a completeness check
15-spring-boot-4-records-over-http.txt MockMvc, Jackson 3, records in and out
16-indicative-timing-vs-handwritten-and-dozer.txt indicative timings; differs on every run
17-error-unmapped-target.txt javac: Unmapped target property
18-error-ambiguous-mapping-methods.txt javac: Ambiguous mapping methods found
19-error-missing-source-property.txt javac: No property named ... Did you mean ...?
20-update-method-on-a-record-compiles-to-a-no-op.txt @MappingTarget on a record compiles, and generates an empty method
21-error-cannot-map-type.txt javac: Can't map property
22-error-decorator-constructor.txt javac: the generated decorator subclass has no constructor to call
23-processor-on-the-plain-classpath.txt JDK 25 compiles without running the processor if it is only on -cp
24-lombok-processor-order.txt Lombok before/after MapStruct, with and without lombok-mapstruct-binding
25-processor-jar-facts.txt the processor service entry and its -A options, read with javap