0013. Migration from MapStruct to Manual Mappers
Status: Accepted
Date: 2026-01-30
Context: MapStruct introduced build cache, CDI, annotation processor, and CI issues; manual mappers are proposed instead.
Context
We implemented MapStruct to handle DTO-to-entity mapping, but it has caused significant build issues:
- Build cache problems: Generated code interferes with incremental builds and Maven build cache
- CDI injection failures: MapStruct-generated implementations sometimes fail to be recognized as CDI
beans, causing
UnsatisfiedResolutionException
- Annotation processing complexity: Requires careful configuration of annotation processors, which can break with Maven/Gradle version changes
- CI/CD unpredictability: Build failures due to stale generated code or annotation processing issues
- Debugging difficulty: Generated code is harder to debug and reason about
The build issues have become more costly than the benefits MapStruct provides.
Alternatives Considered
Why Not Other Mapping Libraries?
We evaluated whether other 3rd party mapping libraries would solve our issues, but determined they would likely face similar problems:
Compile-Time Code Generation Libraries (Selma, JMapper)
- Selma: Similar annotation processing approach to MapStruct
- Would likely have same build cache issues
- Would likely have same CDI injection problems
- Smaller community/less mature than MapStruct
- JMapper: Hybrid approach (runtime + optional compile-time)
- Still uses annotation processing for compile-time mode
- More complex configuration
- Less active community
Conclusion: Other compile-time generators would likely face the same annotation processing and CDI integration issues that plagued MapStruct.
Runtime Reflection-Based Libraries (Dozer, ModelMapper, Orika)
- Dozer: Runtime reflection-based mapping
- Pros: No annotation processing, no build issues
- Cons:
- Significant performance overhead (5-10x slower than MapStruct)
- Runtime errors instead of compile-time safety
- Less type safety
- Declining community support
- ModelMapper: Convention-based runtime mapping
- Pros: No annotation processing, easy configuration
- Cons:
- Runtime reflection overhead (6x slower than MapStruct)
- Convention-based mapping can be unpredictable
- Runtime errors for mismatched fields
- Orika: Runtime bytecode generation
- Pros: Good performance after initial setup, no annotation processing
- Cons:
- Still uses reflection/bytecode generation at runtime
- Less type safety than compile-time solutions
- Moderate community support
Conclusion: Runtime reflection-based mappers avoid build issues but introduce:
- Performance overhead (unacceptable for high-throughput services)
- Loss of compile-time type safety
- Runtime errors that could be caught at compile-time
- Less predictable behavior
Why Manual Mappers?
Given that:
- Compile-time generators (MapStruct, Selma, JMapper) all use annotation processing → same build issues
- Runtime reflection mappers (Dozer, ModelMapper, Orika) avoid build issues but introduce performance and type safety problems
- Manual mappers provide:
- Zero build complexity
- Full type safety
- Best performance (no reflection, no code generation)
- Explicit, debuggable code
- No external dependencies
The decision to use manual mappers was made because:
- The mapping logic is relatively simple (mostly field-to-field)
- The estimated boilerplate (~200-300 lines) is manageable
- Build stability and performance are critical
- Explicit code aligns with clean architecture principles
Decision
We will migrate from MapStruct to manual mapper classes that explicitly handle conversions between DTOs and persistence entities.
Rationale
- Build stability: No annotation processing means no generated code, eliminating build cache issues
- Explicit and debuggable: Manual code is easier to understand, debug, and maintain
- Type safety: Java’s type system provides compile-time safety without code generation
- Clean architecture alignment: Explicit mappers fit better with our clean architecture approach
- Simple maintenance: No special build configuration or annotation processor setup required
- Performance: Manual mappers are as fast as MapStruct (both compile-time, no reflection)
Trade-offs
Pros:
- Zero build-time code generation
- Predictable builds and CI/CD
- Easy to debug and test
- Full control over mapping logic
- No external dependencies for mapping
Cons:
- More boilerplate code (mitigated by keeping mappers small and focused)
- Manual updates when DTOs/entities change (caught by compiler)
- Slightly more code to maintain
Implementation Strategy
Phase 1: Create Reference Implementation
- Migrate
CandidateMapper first as it’s the simplest
- Establish patterns and conventions
- Validate approach works end-to-end
Phase 2: Migrate Document Service Mappers
ResumeMapper (most complex, has JSON processing)
JobSpecMapper
Phase 3: Cleanup
- Remove MapStruct dependencies
- Remove annotation processor configuration
- Clean up generated sources
- Update documentation
Mapper Implementation Pattern
All manual mappers will follow this pattern:
@ApplicationScoped
public class CandidateMapper {
public ActorRecord toRecord(RegisterRequestWithAuthIdentity source) {
// Explicit field mapping
}
public ActorResponse toActorResponse(ActorRecord source) {
// Explicit field mapping
}
}
Principles
- One mapper class per aggregate: Keep mappers focused on one domain concept
- Explicit null handling: Handle nulls explicitly rather than relying on defaults
- Immutable where possible: Prefer creating new objects over mutating
- Testable: Each mapper method should be easily unit testable
- Documented: Complex mappings should have comments explaining business logic
Migration Checklist
Consequences
Positive
- Build stability and predictability
- Easier debugging and maintenance
- No annotation processing complexity
- Better alignment with clean architecture
Negative
- More code to maintain (estimated ~200-300 lines total)
- Manual updates when DTOs change (but compiler catches these)
Neutral
- Performance remains the same (both are compile-time, no reflection)
- Runtime behavior unchanged
References