{"id":2063,"date":"2026-01-19T02:02:47","date_gmt":"2026-01-18T13:02:47","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2063"},"modified":"2026-01-19T02:02:47","modified_gmt":"2026-01-18T13:02:47","slug":"mapstruct-builder-support-with-java-records","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2063","title":{"rendered":"MapStruct Builder Support with Java Records"},"content":{"rendered":"<p>Java records combined with explicit builder patterns provide immutable data classes that work seamlessly with MapStruct's builder detection. This approach leverages records' automatic getters and compact constructors while adding the required builder infrastructure for mapping.<\/p>\n<h2>Complete Record Target Class<\/h2>\n<p>The <code>Person<\/code> record with builder support:<\/p>\n<pre><code class=\"language-java\">public record Person(\n    Long id,\n    String firstName,\n    String lastName\n) {\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static class Builder {\n        private Long id;\n        private String firstName;\n        private String lastName;\n\n        public Builder id(Long id) {\n            this.id = id;\n            return this;\n        }\n\n        public Builder firstName(String firstName) {\n            this.firstName = firstName;\n            return this;\n        }\n\n        public Builder lastName(String lastName) {\n            this.lastName = lastName;\n            return this;\n        }\n\n        public Person build() {\n            return new Person(id, firstName, lastName);\n        }\n    }\n}<\/code><\/pre>\n<h2>Complete Source Class<\/h2>\n<p>Simple mutable source class for demonstration:<\/p>\n<pre><code class=\"language-java\">public class ExternalPerson {\n    private Long id;\n    private String personFirstName;\n    private String personLastName;\n\n    public ExternalPerson(Long id, String personFirstName, String personLastName) {\n        this.id = id;\n        this.personFirstName = personFirstName;\n        this.personLastName = personLastName;\n    }\n\n    \/\/ Getters and setters\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getPersonFirstName() {\n        return personFirstName;\n    }\n\n    public void setPersonFirstName(String personFirstName) {\n        this.personFirstName = personFirstName;\n    }\n\n    public String getPersonLastName() {\n        return personLastName;\n    }\n\n    public void setPersonLastName(String personLastName) {\n        this.personLastName = personLastName;\n    }\n}<\/code><\/pre>\n<h2>Complete Mapper Interface<\/h2>\n<p>MapStruct automatically detects the record's static <code>builder()<\/code> method:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\n\n@Mapper\npublic interface PersonMapper {\n    @Mapping(source = &quot;personFirstName&quot;, target = &quot;firstName&quot;)\n    @Mapping(source = &quot;personLastName&quot;, target = &quot;lastName&quot;)\n    Person toPerson(ExternalPerson source);\n}<\/code><\/pre>\n<h2>Generated Mapping Code<\/h2>\n<p>MapStruct generates efficient builder-based mapping:<\/p>\n<pre><code class=\"language-java\">import javax.annotation.processing.Generated;\n\n@Generated(\n    value = &quot;org.mapstruct.ap.MappingProcessor&quot;,\n    comments = &quot;version: 1.6.3, compiler: IncrementalProcessingEnvironment from gradle-language-java-9.2.0.jar, environment: Java 25 (Oracle Corporation)&quot;\n)\npublic class PersonMapperImpl implements PersonMapper {\n\n    @Override\n    public Person toPerson(ExternalPerson source) {\n        if ( source == null ) {\n            return null;\n        }\n\n        Person.Builder person = Person.builder();\n\n        person.firstName( source.getPersonFirstName() );\n        person.lastName( source.getPersonLastName() );\n        person.id( source.getId() );\n\n        return person.build();\n    }\n}<\/code><\/pre>\n<h2>Custom Builder Method Names<\/h2>\n<p>Override defaults using <code>@Builder<\/code> annotation on the mapper:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.Builder;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\n\n@Mapper(builder = @Builder(buildMethod = &quot;construct&quot;))\npublic abstract class PersonMapper {\n    @Mapping(source = &quot;personFirstName&quot;, target = &quot;firstName&quot;)\n    @Mapping(source = &quot;personLastName&quot;, target = &quot;lastName&quot;)\n    abstract Person toPerson(ExternalPerson source);\n}<\/code><\/pre>\n<p>For this configuration, change the record to:<\/p>\n<pre><code class=\"language-java\">\/\/ In Builder class:\npublic Person construct() {  \/\/ Changed from build()\n    return new Person(id, firstName, lastName);\n}<\/code><\/pre>\n<h2>Key Benefits of Record + Builder Pattern<\/h2>\n<ul>\n<li>Records provide automatic <code>equals()<\/code>, <code>hashCode()<\/code>, <code>toString()<\/code>, and getters<\/li>\n<li>Immutable by default with final components<\/li>\n<li>Builder enables fluent construction for complex initialization<\/li>\n<li>MapStruct auto-detects standard <code>builder()<\/code> + <code>build()<\/code> pattern<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Java records combined with explicit builder patterns provide immutable data classes that work seamlessly with MapStruct&#8217;s builder detection. This approach leverages records&#8217; automatic getters and compact constructors while adding the required builder infrastructure for mapping. Complete Record Target Class The Person record with builder support: public record Person( Long id, String firstName, String lastName ) [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[91],"tags":[],"_links":{"self":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2063"}],"collection":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2063"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2063\/revisions"}],"predecessor-version":[{"id":2064,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2063\/revisions\/2064"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2063"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2063"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2063"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}