Summary
Add built-in support for MapStruct by shipping a org.mapstruct.ap.spi.BuilderProvider implementation, so that builders generated by @SimpleBuilder are automatically detected and used when MapStruct maps to those bean types — analogous to the existing Jackson integration (generateJacksonModule, @JsonDeserialize support), but as a compile-time SPI instead of generated code.
Problem
MapStruct discovers builders through its BuilderProvider SPI. Its DefaultBuilderProvider only scans public static parameterless methods on the bean type itself as builder-creation candidates (e.g. Person.builder()).
Builders generated by simple-builders keep the factory on the builder class instead:
public class PersonDtoBuilder {
public static PersonDtoBuilder create() { ... }
public PersonDto build() { ... }
}
Since PersonDto has no static method returning PersonDtoBuilder, the default provider never finds the builder, and MapStruct silently falls back to constructor/setter mapping.
Proposed solution
Ship a compiled BuilderProvider in the processor jar (or a small add-on module) plus a META-INF/services/org.mapstruct.ap.spi.BuilderProvider registration. MapStruct loads providers from the annotation processor path, so any project that already puts simple-builders and mapstruct-processor on the same processor path gets builder detection for free — no user configuration.
Implementation sketch:
findBuilderInfo(TypeMirror type): for bean com.foo.PersonDto, resolve com.foo.PersonDtoBuilder (same package, simpleName + builderSuffix) via MapStructProcessingEnvironment.getElementUtils()/getTypeUtils().
create() is directly usable: BuilderInfo explicitly allows the creation method to be "a public static method in the builder itself", so return
new BuilderInfo.Builder().builderCreationMethod(create).buildMethod(List.of(build)).build().
- Non-default
builderSuffix: read @SimpleBuilder/@Template config off the bean type, or match generated builders via @BuilderImplementation(forClass = ...).
- Processing rounds: generated builders don't exist when MapStruct first asks — throw
TypeHierarchyErroneousException to defer resolution to the next round (this mechanism exists precisely for builders generated by other processors).
Dependencies
Only a provided/optional compile-time dependency on org.mapstruct:mapstruct-processor (the SPI interfaces live there, not in the mapstruct annotations artifact). The SPI has been stable since MapStruct 1.3. Projects without MapStruct never load the class — the service file is only read by mapstruct-processor.
Prior art
ImmutablesBuilderProvider, shipped inside MapStruct itself, shows the pattern.
- simple-builders' own
example-custom-generator module shows the project's custom-generator SPI; the MapStruct provider is the compile-time counterpart.
Summary
Add built-in support for MapStruct by shipping a
org.mapstruct.ap.spi.BuilderProviderimplementation, so that builders generated by@SimpleBuilderare automatically detected and used when MapStruct maps to those bean types — analogous to the existing Jackson integration (generateJacksonModule,@JsonDeserializesupport), but as a compile-time SPI instead of generated code.Problem
MapStruct discovers builders through its
BuilderProviderSPI. ItsDefaultBuilderProvideronly scanspublic staticparameterless methods on the bean type itself as builder-creation candidates (e.g.Person.builder()).Builders generated by simple-builders keep the factory on the builder class instead:
Since
PersonDtohas no static method returningPersonDtoBuilder, the default provider never finds the builder, and MapStruct silently falls back to constructor/setter mapping.Proposed solution
Ship a compiled
BuilderProviderin the processor jar (or a small add-on module) plus aMETA-INF/services/org.mapstruct.ap.spi.BuilderProviderregistration. MapStruct loads providers from the annotation processor path, so any project that already puts simple-builders andmapstruct-processoron the same processor path gets builder detection for free — no user configuration.Implementation sketch:
findBuilderInfo(TypeMirror type): for beancom.foo.PersonDto, resolvecom.foo.PersonDtoBuilder(same package,simpleName + builderSuffix) viaMapStructProcessingEnvironment.getElementUtils()/getTypeUtils().create()is directly usable:BuilderInfoexplicitly allows the creation method to be "a public static method in the builder itself", so returnnew BuilderInfo.Builder().builderCreationMethod(create).buildMethod(List.of(build)).build().builderSuffix: read@SimpleBuilder/@Templateconfig off the bean type, or match generated builders via@BuilderImplementation(forClass = ...).TypeHierarchyErroneousExceptionto defer resolution to the next round (this mechanism exists precisely for builders generated by other processors).Dependencies
Only a
provided/optionalcompile-time dependency onorg.mapstruct:mapstruct-processor(the SPI interfaces live there, not in themapstructannotations artifact). The SPI has been stable since MapStruct 1.3. Projects without MapStruct never load the class — the service file is only read by mapstruct-processor.Prior art
ImmutablesBuilderProvider, shipped inside MapStruct itself, shows the pattern.example-custom-generatormodule shows the project's custom-generator SPI; the MapStruct provider is the compile-time counterpart.