{"id":2073,"date":"2026-01-19T14:26:30","date_gmt":"2026-01-19T01:26:30","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2073"},"modified":"2026-01-31T01:24:40","modified_gmt":"2026-01-30T12:24:40","slug":"mastering-mapstruct-decorators-with-decoratedwith","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2073","title":{"rendered":"MapStruct Decorators with DecoratedWith"},"content":{"rendered":"<p>MapStruct's <code>@DecoratedWith<\/code> annotation enables extending generated mappers with custom business logic while preserving core mapping functionality.<\/p>\n<h2>Domain Models<\/h2>\n<p><strong>Person.java<\/strong><\/p>\n<pre><code class=\"language-java\">public class Person {\n    private String firstName;\n    private String lastName;\n    private Address address;\n\n    public Person() {}\n\n    public Person(String firstName, String lastName, Address address) {\n        this.firstName = firstName;\n        this.lastName = lastName;\n        this.address = address;\n    }\n\n    \/\/ Getters and setters\n    public String getFirstName() { return firstName; }\n    public void setFirstName(String firstName) { this.firstName = firstName; }\n    public String getLastName() { return lastName; }\n    public void setLastName(String lastName) { this.lastName = lastName; }\n    public Address getAddress() { return address; }\n    public void setAddress(Address address) { this.address = address; }\n}<\/code><\/pre>\n<p><strong>Address.java<\/strong><\/p>\n<pre><code class=\"language-java\">public class Address {\n    private String street;\n    private String city;\n\n    public Address() {}\n    public Address(String street, String city) {\n        this.street = street;\n        this.city = city;\n    }\n\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}<\/code><\/pre>\n<h2>DTO Classes<\/h2>\n<p><strong>PersonDto.java<\/strong><\/p>\n<pre><code class=\"language-java\">public class PersonDto {\n    private String name;  \/\/ Computed by decorator\n    private AddressDto address;\n\n    public PersonDto() {}\n\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}<\/code><\/pre>\n<p><strong>AddressDto.java<\/strong><\/p>\n<pre><code class=\"language-java\">public class AddressDto {\n    private String street;\n    private String city;\n\n    public AddressDto() {}\n\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}<\/code><\/pre>\n<h2>Mapper Interface<\/h2>\n<p><strong>PersonMapper.java<\/strong><\/p>\n<pre><code class=\"language-java\">import org.mapstruct.*;\n\n@Mapper\n@DecoratedWith(PersonMapperDecorator.class)\npublic interface PersonMapper {\n    @Mapping(target = &quot;name&quot;, ignore = true)  \/\/ Handled by decorator\n    PersonDto personToPersonDto(Person person);\n\n    AddressDto addressToAddressDto(Address address);\n}<\/code><\/pre>\n<h2>Decorator Implementation<\/h2>\n<p><strong>PersonMapperDecorator.java<\/strong><\/p>\n<pre><code class=\"language-java\">public abstract class PersonMapperDecorator implements PersonMapper {\n    protected final PersonMapper delegate;\n\n    public PersonMapperDecorator(PersonMapper delegate) {\n        this.delegate = delegate;\n    }\n\n    @Override\n    public PersonDto personToPersonDto(Person person) {\n        \/\/ Delegate basic mapping first\n        PersonDto dto = delegate.personToPersonDto(person);\n\n        \/\/ Custom business logic: concatenate names\n        dto.setName(person.getFirstName() + &quot; &quot; + person.getLastName());\n\n        return dto;\n    }\n\n    \/\/ Other methods delegate automatically (remains abstract)\n}<\/code><\/pre>\n<h2>Usage Example<\/h2>\n<p><strong>Main.java<\/strong><\/p>\n<pre><code class=\"language-java\">import org.mapstruct.factory.Mappers;\n\npublic class Main {\n    public static void main(String[] args) {\n        PersonMapper mapper = Mappers.getMapper(PersonMapper.class);\n\n        Address address = new Address(&quot;123 Main St&quot;, &quot;Springfield&quot;);\n        Person person = new Person(&quot;John&quot;, &quot;Doe&quot;, address);\n\n        \/\/ Uses decorator: name becomes &quot;John Doe&quot;\n        PersonDto dto = mapper.personToPersonDto(person);\n        System.out.println(&quot;Name: &quot; + dto.getName());           \/\/ John Doe\n        System.out.println(&quot;Street: &quot; + dto.getAddress().getStreet());  \/\/ 123 Main St\n\n        \/\/ Direct delegation (no custom logic)\n        AddressDto addrDto = mapper.addressToAddressDto(address);\n        System.out.println(&quot;City: &quot; + addrDto.getCity());       \/\/ Springfield\n    }\n}<\/code><\/pre>\n<h2>How It Works<\/h2>\n<p>MapStruct generates <code>PersonMapperImpl<\/code> handling all standard mappings. The decorator wraps this implementation via constructor injection. <code>Mappers.getMapper()<\/code> automatically returns the decorator instance containing the generated delegate. Only overridden methods (<code>personToPersonDto<\/code>) execute custom logic; others delegate transparently. This pattern ensures thread-safety, testability, and framework independence.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>MapStruct&#8217;s @DecoratedWith annotation enables extending generated mappers with custom business logic while preserving core mapping functionality. Domain Models Person.java public class Person { private String firstName; private String lastName; private Address address; public Person() {} public Person(String firstName, String lastName, Address address) { this.firstName = firstName; this.lastName = lastName; this.address = address; } \/\/ Getters [&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\/2073"}],"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=2073"}],"version-history":[{"count":2,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2073\/revisions"}],"predecessor-version":[{"id":2078,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2073\/revisions\/2078"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2073"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2073"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2073"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}