{"id":2061,"date":"2026-01-19T01:45:41","date_gmt":"2026-01-18T12:45:41","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2061"},"modified":"2026-01-19T01:45:41","modified_gmt":"2026-01-18T12:45:41","slug":"mapstruct-context-guide-with-lifecycle-objectfactory-examples","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2061","title":{"rendered":"MapStruct Context: Guide with Lifecycle &#038; ObjectFactory Examples"},"content":{"rendered":"<p>MapStruct's @Context annotation enables passing contextual data\u2014like locales, services, or user state\u2014through mapper method calls without treating it as source or target input. This propagates automatically to lifecycle methods (@BeforeMapping, @AfterMapping, @ObjectFactory) and custom qualifiers, making it ideal for complex mappings requiring external dependencies.<\/p>\n<h2>Core Concepts<\/h2>\n<p>@Context marks parameters for transparent propagation: upstream mapper methods forward them to downstream calls, including generated code, custom methods, and lifecycle hooks. Callers must supply values explicitly, as MapStruct performs no instantiation or defaulting. Propagation requires matching @Context declarations across methods, with type compatibility enforced.<\/p>\n<p>Key benefits include locale-aware formatting, injecting services for lookups, or stateful transformations\u2014all without cluttering method signatures with non-mapping data.<\/p>\n<h2>Complete Runnable Example<\/h2>\n<p>Save as <code>ContextDemo.java<\/code>. <\/p>\n<pre><code class=\"language-java\">import org.mapstruct.*;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.Locale;\nimport java.time.format.DateTimeFormatter;\nimport java.time.LocalDate;\n\n\/\/ Source and Target DTOs\nclass Car {\n    private String manufacturer;\n    private String model;\n    private LocalDate built;\n\n    public Car(String manufacturer, String model, LocalDate built) {\n        this.manufacturer = manufacturer;\n        this.model = model;\n        this.built = built;\n    }\n\n    \/\/ Getters\/Setters\n    public String getManufacturer() { return manufacturer; }\n    public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; }\n    public String getModel() { return model; }\n    public void setModel(String model) { this.model = model; }\n    public LocalDate getBuilt() { return built; }\n    public void setBuilt(LocalDate built) { this.built = built; }\n}\n\nclass CarDto {\n    private String make;\n    private String fullModel;\n    private String formattedBuildDate;\n    private String contextInfo;\n\n    \/\/ Getters\/Setters\n    public String getMake() { return make; }\n    public void setMake(String make) { this.make = make; }\n    public String getFullModel() { return fullModel; }\n    public void setFullModel(String fullModel) { this.fullModel = fullModel; }\n    public String getFormattedBuildDate() { return formattedBuildDate; }\n    public void setFormattedBuildDate(String formattedBuildDate) { this.formattedBuildDate = formattedBuildDate; }\n    public String getContextInfo() { return contextInfo; }\n    public void setContextInfo(String contextInfo) { this.contextInfo = contextInfo; }\n}\n\n\/\/ Mapper with full lifecycle + ObjectFactory\n@Mapper(uses = LocalDateUtils.class)\nabstract class CarMapper {\n    \/\/ Top-level entry point - caller provides @Context\n    @Mapping(source = &quot;manufacturer&quot;, target = &quot;make&quot;)\n    @Mapping(source = &quot;built&quot;, target = &quot;formattedBuildDate&quot;, qualifiedByName = &quot;formatDate&quot;)\n    @Mapping(target = &quot;fullModel&quot;, ignore = true)  \/\/ Set in @AfterMapping\n    @Mapping(target = &quot;contextInfo&quot;, ignore = true)  \/\/ Set in @ObjectFactory\n    public abstract CarDto toDto(Car car, @Context Locale locale, @Context String userId);\n\n    \/\/ ObjectFactory receives @Context for custom instantiation\n    @ObjectFactory\n    public CarDto createCarDto(@Context Locale locale, @Context String userId) {\n        CarDto dto = new CarDto();\n        dto.setContextInfo(&quot;Created for user: &quot; + userId + &quot; in locale: &quot; + locale.getDisplayName());\n        System.out.println(&quot;ObjectFactory: Initialized DTO with context - &quot; + userId);\n        return dto;\n    }\n\n    \/\/ Lifecycle methods receive @Context automatically\n    @BeforeMapping\n    void beforeMapping(Car car, @MappingTarget CarDto dto, @Context Locale locale, @Context String userId) {\n        System.out.println(&quot;BeforeMapping: Pre-processing with &quot; + locale.getDisplayName() + &quot; for user &quot; + userId);\n    }\n\n    @AfterMapping\n    void afterMapping(Car car, @MappingTarget CarDto dto, @Context Locale locale, @Context String userId) {\n        System.out.println(&quot;AfterMapping: Finalizing with &quot; + locale.getDisplayName() + &quot; for user &quot; + userId);\n        dto.setFullModel(dto.getMake() + &quot; &quot; + car.getModel() + &quot; (&quot; + locale.getCountry() + &quot;)&quot;);\n    }\n}\n\n\/\/ Custom qualifier helper (also receives @Context)\nclass LocalDateUtils {\n    @Named(&quot;formatDate&quot;)\n    public static String format(LocalDate date, @Context Locale locale) {\n        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;MMM yyyy&quot;, locale);\n        return date.format(formatter);\n    }\n}\n\n\/\/ Demo runner\nclass ContextDemo {\n    public static void main(String[] args) {\n        CarMapper mapper = Mappers.getMapper(CarMapper.class);\n\n        Car car = new Car(&quot;Toyota&quot;, &quot;Camry&quot;, LocalDate.of(2020, 5, 15));\n\n        \/\/ Provide @Context explicitly (Locale + custom userId)\n        CarDto dto = mapper.toDto(car, Locale.forLanguageTag(&quot;de-DE&quot;), &quot;john.doe@company.com&quot;);\n\n        System.out.println(&quot;\\n=== Final Result ===&quot;);\n        System.out.println(&quot;Make: &quot; + dto.getMake());\n        System.out.println(&quot;Full Model: &quot; + dto.getFullModel());\n        System.out.println(&quot;Build Date: &quot; + dto.getFormattedBuildDate());\n        System.out.println(&quot;Context Info: &quot; + dto.getContextInfo());\n    }\n}<\/code><\/pre>\n<p><strong>Expected output showing full lifecycle flow:<\/strong><\/p>\n<pre><code class=\"language-shell\">ObjectFactory: Initialized DTO with context - john.doe@company.com\nBeforeMapping: Pre-processing with German (Germany) for user john.doe@company.com\nAfterMapping: Finalizing with German (Germany) for user john.doe@company.com\n\n=== Final Result ===\nMake: Toyota\nFull Model: Toyota Camry (DE)\nBuild Date: Mai 2020\nContext Info: Created for user: john.doe@company.com in locale: German (Germany)<\/code><\/pre>\n<h2>Lifecycle Integration Details<\/h2>\n<p>The complete flow works as: <strong>ObjectFactory<\/strong> \u2192 <strong>@BeforeMapping<\/strong> \u2192 <strong>property mappings<\/strong> \u2192 <strong>@AfterMapping<\/strong>. Each receives @Context parameters automatically when declared with matching signatures. @ObjectFactory handles target instantiation (useful for dependency injection), while lifecycle methods enable pre\/post-processing with full context awareness.<\/p>\n<p>This pattern scales perfectly for enterprise applications requiring audit trails, localization, or service lookups during mapping.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>MapStruct&#8217;s @Context annotation enables passing contextual data\u2014like locales, services, or user state\u2014through mapper method calls without treating it as source or target input. This propagates automatically to lifecycle methods (@BeforeMapping, @AfterMapping, @ObjectFactory) and custom qualifiers, making it ideal for complex mappings requiring external dependencies. Core Concepts @Context marks parameters for transparent propagation: upstream mapper methods [&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\/2061"}],"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=2061"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2061\/revisions"}],"predecessor-version":[{"id":2062,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2061\/revisions\/2062"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2061"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2061"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2061"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}