{"id":2059,"date":"2026-01-19T01:22:34","date_gmt":"2026-01-18T12:22:34","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2059"},"modified":"2026-01-19T01:22:34","modified_gmt":"2026-01-18T12:22:34","slug":"mapstruct-mapper-composition-pure-java","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2059","title":{"rendered":"MapStruct Mapper Composition: Pure Java"},"content":{"rendered":"<p>Mapper composition in MapStruct creates modular mappings by delegating between mappers using <code>@Mapper(uses = {...})<\/code>. The examples below are fully self-contained, compilable classes with complete domain models, mappers, and main methods.<\/p>\n<h2>Complete Domain Classes<\/h2>\n<pre><code class=\"language-java\">import java.time.LocalDate;\n\n\/\/ Source domain classes\npublic class Address {\n    private String street;\n    private String city;\n\n    public Address(String street, String city) {\n        this.street = street;\n        this.city = city;\n    }\n\n    \/\/ Getters and setters\n    public String getStreet() { return street; }\n    public void setStreet(String street) { this.street = street; }\n    public String getCity() { return city; }\n    public void setCity(String city) { this.city = city; }\n}\n\npublic class User {\n    private String name;\n    private Address address;\n\n    public User(String name, Address address) {\n        this.name = name;\n        this.address = address;\n    }\n\n    \/\/ Getters and setters\n    public String getName() { return name; }\n    public void setName(String name) { this.name = name; }\n    public Address getAddress() { return address; }\n    public void setAddress(Address address) { this.address = address; }\n}\n\n\/\/ Extended User for annotation example\npublic class UserEnriched {\n    private String firstName;\n    private String lastName;\n    private int age;\n    private Address address;\n\n    public UserEnriched(String firstName, String lastName, int age, Address address) {\n        this.firstName = firstName;\n        this.lastName = lastName;\n        this.age = age;\n        this.address = address;\n    }\n\n    \/\/ Getters\n    public String getFirstName() { return firstName; }\n    public String getLastName() { return lastName; }\n    public int getAge() { return age; }\n    public void setAge(int age) { this.age = age; }\n    public Address getAddress() { return address; }\n}\n\n\/\/ Target DTO classes\npublic class AddressDto {\n    private String street;\n    private String city;\n\n    public AddressDto() {}\n\n    \/\/ Getters and setters\n    public String getStreet() { return street; }\n    public void setStreet(String street) { this.street = street; }\n    public String getCity() { return city; }\n    public void setCity(String city) { this.city = city; }\n\n    @Override\n    public String toString() {\n        return street + &quot;, &quot; + city;\n    }\n}\n\npublic class UserDto {\n    private String name;\n    private AddressDto address;\n\n    public UserDto() {}\n\n    \/\/ Getters and setters\n    public String getName() { return name; }\n    public void setName(String name) { this.name = name; }\n    public AddressDto getAddress() { return address; }\n    public void setAddress(AddressDto address) { this.address = address; }\n\n    @Override\n    public String toString() {\n        return name + &quot; &lt;&quot; + address + &quot;&gt;&quot;;\n    }\n}\n\npublic class UserEnrichedDto {\n    private String fullName;\n    private String ageGroup;\n    private AddressDto address;\n\n    public UserEnrichedDto() {}\n\n    \/\/ Getters and setters\n    public String getFullName() { return fullName; }\n    public void setFullName(String fullName) { this.fullName = fullName; }\n    public String getAgeGroup() { return ageGroup; }\n    public void setAgeGroup(String ageGroup) { this.ageGroup = ageGroup; }\n    public AddressDto getAddress() { return address; }\n    public void setAddress(AddressDto address) { this.address = address; }\n\n    @Override\n    public String toString() {\n        return fullName + &quot; (&quot; + ageGroup + &quot;) &lt;&quot; + address + &quot;&gt;&quot;;\n    }\n}<\/code><\/pre>\n<h2>Basic Composition Example<\/h2>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n@Mapper\npublic interface AddressMapper {\n    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);\n\n    AddressDto toDto(Address address);\n}\n\n@Mapper(uses = AddressMapper.class)\npublic interface UserMapper {\n    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);\n\n    UserDto toDto(User user);\n}\n\n\/\/ Runnable test\npublic class BasicComposition {\n    public static void main(String[] args) {\n        User user = new User(&quot;John Doe&quot;,\n            new Address(&quot;123 Main St&quot;, &quot;Auckland&quot;));\n\n        UserDto dto = UserMapper.INSTANCE.toDto(user);\n\n        System.out.println(dto);\n        \/\/ Output: John Doe &lt;123 Main St, Auckland&gt;\n    }\n}<\/code><\/pre>\n<h2>Multiple Mapper and Custom Conversion Composition<\/h2>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.factory.Mappers;\nimport java.time.format.DateTimeFormatter;\nimport java.time.LocalDate;\n\npublic class DateUtils {\n    public static String toString(LocalDate date) {\n        if (date == null) {\n            return null;\n        }\n        return date.format(DateTimeFormatter.ISO_LOCAL_DATE);\n    }\n}\n\npublic class Event {\n    private String title;\n    private LocalDate date;\n    private Address address;\n\n    public Event(String title, LocalDate date, Address address) {\n        this.title = title;\n        this.date = date;\n        this.address = address;\n    }\n\n    public String getTitle() { return title; }\n    public void setTitle(String title) { this.title = title; }\n    public LocalDate getDate() { return date; }\n    public void setDate(LocalDate date) { this.date = date; }\n    public Address getAddress() { return address; }\n    public void setAddress(Address address) { this.address = address; }\n}\n\npublic class EventDto {\n    private String title;\n    private String dateString;\n    private AddressDto address;\n\n    public EventDto() {}\n\n    public String getTitle() { return title; }\n    public void setTitle(String title) { this.title = title; }\n    public String getDateString() { return dateString; }\n    public void setDateString(String dateString) { this.dateString = dateString; }\n    public AddressDto getAddress() { return address; }\n    public void setAddress(AddressDto address) { this.address = address; }\n\n    @Override\n    public String toString() {\n        return title + &quot; on &quot; + dateString + &quot; at &quot; + address;\n    }\n}\n\n@Mapper(uses = {AddressMapper.class, DateUtils.class})\npublic interface EventMapper {\n    EventMapper INSTANCE = Mappers.getMapper(EventMapper.class);\n\n    @Mapping(source = &quot;date&quot;, target = &quot;dateString&quot;)\n    EventDto toDto(Event event);\n}\n\n\/\/ Runnable test\npublic class CustomComposition {\n    public static void main(String[] args) {\n        Event event = new Event(&quot;Tech Conference&quot;,\n            LocalDate.of(2026, 3, 15),\n            new Address(&quot;Convention Centre&quot;, &quot;Auckland&quot;));\n\n        EventDto dto = EventMapper.INSTANCE.toDto(event);\n\n        System.out.println(dto);\n        \/\/ Output: Tech Conference on 2026-03-15 at Convention Centre, Auckland\n    }\n}<\/code><\/pre>\n<h2>Advanced: Reusable Annotation Composition<\/h2>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Named;\nimport org.mapstruct.Qualifier;\nimport org.mapstruct.factory.Mappers;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n\/\/ Custom utility for age grouping\npublic class AgeUtils {\n    @Named(&quot;ageToGroup&quot;)\n    public static String ageToGroup(int age) {\n        if (age &lt; 18) return &quot;Minor&quot;;\n        if (age &lt; 65) return &quot;Adult&quot;;\n        return &quot;Senior&quot;;\n    }\n}\n\n\/\/ 1. Custom composed annotation (bundles multiple @Mapping rules)\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.CLASS)\n@Mapping(target = &quot;fullName&quot;, expression = &quot;java(user.getFirstName() + \\&quot; \\&quot; + user.getLastName())&quot;)\n@Mapping(target = &quot;ageGroup&quot;, source = &quot;age&quot;, qualifiedByName = &quot;ageToGroup&quot;)\npublic @interface UserEnrichment {}\n\n\/\/ 2. Mapper using the composed annotation\n@Mapper(uses = {AddressMapper.class, AgeUtils.class})\npublic interface UserEnrichedMapper {\n    UserEnrichedMapper INSTANCE = Mappers.getMapper(UserEnrichedMapper.class);\n\n    @UserEnrichment  \/\/ &lt;- Single annotation replaces 3+ @Mapping entries!\n    UserEnrichedDto toEnrichedDto(UserEnriched user);\n}\n\n\/\/ Runnable test\npublic class AnnotationComposition {\n    public static void main(String[] args) {\n        UserEnriched user = new UserEnriched(\n            &quot;John&quot;, &quot;Doe&quot;, 42,\n            new Address(&quot;123 Main St&quot;, &quot;Auckland&quot;)\n        );\n\n        UserEnrichedDto dto = UserEnrichedMapper.INSTANCE.toEnrichedDto(user);\n\n        System.out.println(dto);\n        \/\/ Output: John Doe (Adult) &lt;123 Main St, Auckland&gt;\n    }\n}<\/code><\/pre>\n<h2>Global Configuration<\/h2>\n<p>See the <a href=\"https:\/\/www.ronella.xyz\/?p=2057\">Shared MapperConfig in MapStruct: Pure Java<\/a><\/p>\n<h2>Key Benefits Summary<\/h2>\n<table>\n<thead>\n<tr>\n<th>Feature<\/th>\n<th>Pure Java Benefit<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>uses = {...}<\/code><\/td>\n<td>Automatic instantiation, no DI container<\/td>\n<\/tr>\n<tr>\n<td><code>Mappers.getMapper()<\/code><\/td>\n<td>Single point of access per mapper<\/td>\n<\/tr>\n<tr>\n<td><code>@MapperConfig<\/code><\/td>\n<td>Consistent behavior without framework<\/td>\n<\/tr>\n<tr>\n<td>Multiple <code>uses<\/code><\/td>\n<td>Type-based delegation selection<\/td>\n<\/tr>\n<tr>\n<td><code>@UserEnrichment<\/code><\/td>\n<td>Zero-boilerplate reusable mapping rules<\/td>\n<\/tr>\n<tr>\n<td>Custom utils<\/td>\n<td>Seamless integration with plain classes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each example compiles and runs independently after MapStruct annotation processing, demonstrating self-contained mapper hierarchies without external dependencies.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mapper composition in MapStruct creates modular mappings by delegating between mappers using @Mapper(uses = {&#8230;}). The examples below are fully self-contained, compilable classes with complete domain models, mappers, and main methods. Complete Domain Classes import java.time.LocalDate; \/\/ Source domain classes public class Address { private String street; private String city; public Address(String street, String city) [&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\/2059"}],"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=2059"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2059\/revisions"}],"predecessor-version":[{"id":2060,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2059\/revisions\/2060"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2059"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2059"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2059"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}