Skip to content

MapStruct integration: ship a BuilderProvider SPI so generated builders are auto-detected #300

Description

@igel-devin-ai

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions