{"id":2057,"date":"2026-01-18T20:13:09","date_gmt":"2026-01-18T07:13:09","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2057"},"modified":"2026-01-18T20:13:09","modified_gmt":"2026-01-18T07:13:09","slug":"shared-mapperconfig-in-mapstruct-pure-java","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2057","title":{"rendered":"Shared MapperConfig in MapStruct: Pure Java"},"content":{"rendered":"<p>MapStruct's <code>@MapperConfig<\/code> centralizes common mapping rules, global settings, and shared utilities across multiple mappers. Without Spring, mappers generate plain POJOs for manual instantiation.<\/p>\n<h2>Why Shared @MapperConfig?<\/h2>\n<ul>\n<li><strong>DRY principle<\/strong>: Define <code>ignore<\/code> rules, <code>nullValuePropertyMappingStrategy<\/code>, etc. once<\/li>\n<li><strong>Consistency<\/strong>: All mappers share identical base behavior<\/li>\n<li><strong>Inheritance<\/strong>: Prototype methods propagate <code>@Mapping<\/code> annotations to concrete mappers<\/li>\n<li><strong>Flexibility<\/strong>: Works as interface (pure config) or abstract class (config + utilities)<\/li>\n<\/ul>\n<h2>Complete Domain Models<\/h2>\n<pre><code class=\"language-java\">public class BaseDto {\n    private Long id;\n    private Long createdAt;\n    private Long version;\n\n    \/\/ constructors, getters, setters\n    public BaseDto() {}\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public Long getCreatedAt() {\n        return createdAt;\n    }\n\n    public void setCreatedAt(Long createdAt) {\n        this.createdAt = createdAt;\n    }\n\n    public Long getVersion() {\n        return version;\n    }\n\n    public void setVersion(Long version) {\n        this.version = version;\n    }\n}\n\npublic class BaseEntity {\n    private Long id;\n    private Long createdAt;\n    private Long version;\n    private Long lastModified;\n\n    \/\/ constructors, getters, setters\n    public BaseEntity() {}\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public Long getCreatedAt() {\n        return createdAt;\n    }\n\n    public void setCreatedAt(Long createdAt) {\n        this.createdAt = createdAt;\n    }\n\n    public Long getVersion() {\n        return version;\n    }\n\n    public void setVersion(Long version) {\n        this.version = version;\n    }\n\n    public Long getLastModified() {\n        return lastModified;\n    }\n\n    public void setLastModified(Long lastModified) {\n        this.lastModified = lastModified;\n    }\n}\n\npublic class CustomerDto extends BaseDto {\n    private String firstName;\n    private String lastName;\n    private String email;\n\n    \/\/ constructors, getters, setters\n    public String getFirstName() {\n        return firstName;\n    }\n\n    public void setFirstName(String firstName) {\n        this.firstName = firstName;\n    }\n\n    public String getLastName() {\n        return lastName;\n    }\n\n    public void setLastName(String lastName) {\n        this.lastName = lastName;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n}\n\npublic class Customer extends BaseEntity {\n    private String firstName;\n    private String lastName;\n    private String customerEmail;\n\n    \/\/ constructors, getters, setters\n    public String getFirstName() {\n        return firstName;\n    }\n\n    public void setFirstName(String firstName) {\n        this.firstName = firstName;\n    }\n\n    public String getLastName() {\n        return lastName;\n    }\n\n    public void setLastName(String lastName) {\n        this.lastName = lastName;\n    }\n\n    public String getCustomerEmail() {\n        return customerEmail;\n    }\n\n    public void setCustomerEmail(String customerEmail) {\n        this.customerEmail = customerEmail;\n    }\n}\n\npublic class OrderDto extends BaseDto {\n    private String orderNumber;\n    private CustomerDto customer;\n\n    \/\/ constructors, getters, setters\n    public String getOrderNumber() {\n        return orderNumber;\n    }\n\n    public void setOrderNumber(String orderNumber) {\n        this.orderNumber = orderNumber;\n    }\n\n    public CustomerDto getCustomer() {\n        return customer;\n    }\n\n    public void setCustomer(CustomerDto customer) {\n        this.customer = customer;\n    }\n}\n\npublic class Order extends BaseEntity {\n    private String orderNumber;\n    private String customerEmail;\n    private String status;\n\n    \/\/ constructors, getters, setters\n    public String getOrderNumber() {\n        return orderNumber;\n    }\n\n    public void setOrderNumber(String orderNumber) {\n        this.orderNumber = orderNumber;\n    }\n\n    public String getCustomerEmail() {\n        return customerEmail;\n    }\n\n    public void setCustomerEmail(String customerEmail) {\n        this.customerEmail = customerEmail;\n    }\n\n    public String getStatus() {\n        return status;\n    }\n\n    public void setStatus(String status) {\n        this.status = status;\n    }\n}<\/code><\/pre>\n<h2>@MapperConfig as Interface (Pure Configuration)<\/h2>\n<p><strong>GlobalMapperConfig.java<\/strong>:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.MapperConfig;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.ReportingPolicy;\nimport org.mapstruct.MappingInheritanceStrategy;\n\n@MapperConfig(\n    unmappedTargetPolicy = ReportingPolicy.ERROR,\n    mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG\n)\npublic interface GlobalMapperConfig {\n\n    @Mapping(target = &quot;id&quot;, ignore = true)\n    @Mapping(target = &quot;createdAt&quot;, ignore = true)\n    @Mapping(target = &quot;version&quot;, ignore = true)\n    BaseEntity toBaseEntity(BaseDto dto);\n}<\/code><\/pre>\n<p><strong>CustomerMapper.java<\/strong>:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.MappingTarget;\n\n@Mapper(config = GlobalMapperConfig.class)\npublic interface CustomerMapper {\n\n    \/\/ AUTO inherits ignore rules for id\/createdAt\/version\n    @Mapping(target = &quot;customerEmail&quot;, source = &quot;email&quot;)\n    @Mapping(target = &quot;lastModified&quot;, ignore = true)\n    Customer toEntity(CustomerDto dto);\n\n    @Mapping(target = &quot;customerEmail&quot;, source = &quot;email&quot;)\n    @Mapping(target = &quot;lastModified&quot;, ignore = true)\n    void updateEntity(CustomerDto dto, @MappingTarget Customer entity);\n}<\/code><\/pre>\n<p><strong>Generated CustomerMapperImpl.java<\/strong>:<\/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 CustomerMapperImpl implements CustomerMapper {\n\n    @Override\n    public Customer toEntity(CustomerDto dto) {\n        if ( dto == null ) {\n            return null;\n        }\n\n        Customer customer = new Customer();\n\n        customer.setCustomerEmail( dto.getEmail() );\n        customer.setFirstName( dto.getFirstName() );\n        customer.setLastName( dto.getLastName() );\n\n        return customer;\n    }\n\n    @Override\n    public void updateEntity(CustomerDto dto, Customer entity) {\n        if ( dto == null ) {\n            return;\n        }\n\n        entity.setCustomerEmail( dto.getEmail() );\n        entity.setFirstName( dto.getFirstName() );\n        entity.setLastName( dto.getLastName() );\n    }\n}<\/code><\/pre>\n<h2>@MapperConfig in Abstract Mapper Class<\/h2>\n<p><strong>BaseEntityMapper.java<\/strong>:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.BeforeMapping;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.MappingTarget;\nimport org.mapstruct.Named;\n\n@Mapper(config = GlobalMapperConfig.class)\npublic abstract class BaseEntityMapper {\n\n    \/\/ Inherits GlobalMapperConfig ignore rules\n    @Mapping(target = &quot;lastModified&quot;, ignore = true)\n    public abstract BaseEntity toEntity(BaseDto dto);\n\n    \/\/ Shared utility method\n    @Named(&quot;normalizeEmail&quot;)\n    protected String normalizeEmail(String email) {\n        return email != null ? email.trim().toLowerCase() : null;\n    }\n\n    @Mapping(target = &quot;lastModified&quot;, ignore = true)\n    public abstract void updateEntity(BaseDto dto, @MappingTarget BaseEntity entity);\n\n    @BeforeMapping\n    protected void enrichTimestamps(BaseDto dto, @MappingTarget BaseEntity entity) {\n        entity.setLastModified(System.currentTimeMillis());\n    }\n}<\/code><\/pre>\n<p><strong>Generated BaseEntityMapperImpl.java<\/strong>:<\/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 BaseEntityMapperImpl extends BaseEntityMapper {\n\n    @Override\n    public BaseEntity toEntity(BaseDto dto) {\n        if ( dto == null ) {\n            return null;\n        }\n\n        BaseEntity baseEntity = new BaseEntity();\n\n        enrichTimestamps( dto, baseEntity );\n\n        return baseEntity;\n    }\n\n    @Override\n    public void updateEntity(BaseDto dto, BaseEntity entity) {\n        if ( dto == null ) {\n            return;\n        }\n\n        enrichTimestamps( dto, entity );\n    }\n}<\/code><\/pre>\n<p><strong>OrderMapper.java<\/strong>:<\/p>\n<pre><code class=\"language-java\">import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\n\n@Mapper(config = GlobalMapperConfig.class, uses = BaseEntityMapper.class)\npublic abstract class OrderMapper {\n\n    \/\/ Inherits GlobalConfig + BaseEntityMapper utilities\n    @Mapping(target = &quot;customerEmail&quot;, source = &quot;customer.email&quot;, qualifiedByName = &quot;normalizeEmail&quot;)\n    @Mapping(target = &quot;status&quot;, constant = &quot;PENDING&quot;)\n    @Mapping(target = &quot;lastModified&quot;, ignore = true)\n    public abstract Order toEntity(OrderDto dto);\n\n    public Order createProcessedOrder(OrderDto dto) {\n        Order order = toEntity(dto);\n        order.setStatus(&quot;PROCESSED&quot;);\n        return order;\n    }\n}<\/code><\/pre>\n<p><strong>Generated OrderMapperImpl.java<\/strong>:<\/p>\n<pre><code class=\"language-java\">import javax.annotation.processing.Generated;\nimport org.mapstruct.factory.Mappers;\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 OrderMapperImpl extends OrderMapper {\n\n    private final BaseEntityMapper baseEntityMapper = Mappers.getMapper( BaseEntityMapper.class );\n\n    @Override\n    public Order toEntity(OrderDto dto) {\n        if ( dto == null ) {\n            return null;\n        }\n\n        Order order = new Order();\n\n        baseEntityMapper.enrichTimestamps( dto, order );\n\n        order.setCustomerEmail( baseEntityMapper.normalizeEmail( dtoCustomerEmail( dto ) ) );\n        order.setOrderNumber( dto.getOrderNumber() );\n\n        order.setStatus( &quot;PENDING&quot; );\n\n        return order;\n    }\n\n    private String dtoCustomerEmail(OrderDto orderDto) {\n        CustomerDto customer = orderDto.getCustomer();\n        if ( customer == null ) {\n            return null;\n        }\n        return customer.getEmail();\n    }\n}<\/code><\/pre>\n<h2>Usage in Application Code<\/h2>\n<pre><code class=\"language-java\">public class OrderService {\n    private final CustomerMapper customerMapper = new CustomerMapperImpl();\n    private final OrderMapper orderMapper = new OrderMapperImpl();\n\n    public Customer createCustomer(CustomerDto dto) {\n        return customerMapper.toEntity(dto);\n    }\n\n    public Order processOrder(OrderDto dto) {\n        \/\/ Gets 3 layers of configuration\n        return orderMapper.createProcessedOrder(dto);\n    }\n}\n\n\/\/ Fluent factory (alternative)\n\/\/ import org.mapstruct.factory.Mappers;\n\/\/ CustomerMapper mapper = Mappers.getMapper(CustomerMapper.class);\n\/\/ Customer customer = mapper.toEntity(dto);<\/code><\/pre>\n<h2>Inheritance Layers Summary<\/h2>\n<table>\n<thead>\n<tr>\n<th>Layer<\/th>\n<th>Source<\/th>\n<th>Provides<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>GlobalMapperConfig<\/strong><\/td>\n<td>Interface<\/td>\n<td><code>id\/createdAt\/version<\/code> ignore rules<\/td>\n<\/tr>\n<tr>\n<td><strong>BaseEntityMapper<\/strong><\/td>\n<td>Abstract class<\/td>\n<td>+ <code>normalizeEmail()<\/code>, <code>@BeforeMapping<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>CustomerMapper<\/strong><\/td>\n<td>Interface<\/td>\n<td>Specific <code>Customer<\/code> mappings<\/td>\n<\/tr>\n<tr>\n<td><strong>OrderMapper<\/strong><\/td>\n<td>Abstract class<\/td>\n<td>Specific <code>Order<\/code> mappings + <code>createProcessedOrder()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Key Benefits<\/h2>\n<ol>\n<li><strong>Scalable hierarchy<\/strong> - Global \u2192 Base \u2192 Specific mappers<\/li>\n<li><strong>Full type safety<\/strong> - No runtime surprises<\/li>\n<\/ol>\n<p>This pattern handles dozens of entities while maintaining clean, consistent mapping behavior across your entire application.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>MapStruct&#8217;s @MapperConfig centralizes common mapping rules, global settings, and shared utilities across multiple mappers. Without Spring, mappers generate plain POJOs for manual instantiation. Why Shared @MapperConfig? DRY principle: Define ignore rules, nullValuePropertyMappingStrategy, etc. once Consistency: All mappers share identical base behavior Inheritance: Prototype methods propagate @Mapping annotations to concrete mappers Flexibility: Works as interface (pure [&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\/2057"}],"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=2057"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2057\/revisions"}],"predecessor-version":[{"id":2058,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2057\/revisions\/2058"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2057"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}