Tired of writing endless, error-prone boilerplate code just to map data between your Java DTOs, Entities, and other beans? We've all been there. It's tedious, hard to maintain, and a common source of bugs.
What if I told you there's a way to make this process almost magical?
Enter MapStruct, a Java annotation processor that generates type-safe bean mapping code for you at compile time. No reflection, no runtime overhead — just clean, efficient, and easy-to-maintain mappers.
If you're looking to streamline your Java development, reduce bugs, and write cleaner code, you're in the right place.
What Exactly IS MapStruct?
MapStruct isn't another runtime mapping library that relies on reflection. Instead, it's a code generator that plugs into the Java compiler.
You define an interface and annotate it with MapStruct annotations. During compilation, MapStruct generates an implementation of that interface with all the necessary mapping logic.
Key Advantages
Type Safety – Mapping errors are detected at compile time.
High Performance – Generated code is plain Java with no reflection.
Easy to Debug – Generated code is readable and debuggable.
Why Choose MapStruct Over Manual Mapping or Other Libraries?
MapStruct stands out because it provides:
- Reduced Boilerplate
- Compile-Time Safety
- High Performance
- Clear Declarative Mapping
- Excellent Integration with frameworks like Spring and Lombok
Powerful Features
MapStruct supports:
- Mapping different field names
- Nested bean mapping
- Collection mapping
- Type conversions
- Custom mapping methods
- Default values and expressions
Getting Started: Setting Up MapStruct
Maven Configuration
<properties>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<lombok.version>1.18.30</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Core Concepts & Examples
1. Basic Mapping
public class User {
private Long id;
private String firstName;
private String lastName;
private String email;
}
public class UserDto {
private Long id;
private String firstName;
private String lastName;
private String email;
}
Mapper
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDto userToUserDto(User user);
User userDtoToUser(UserDto userDto);
}
Usage:
User user = new User();
UserDto dto = UserMapper.INSTANCE.userToUserDto(user);
2. Handling Different Field Names
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
@Mapping(source = "ageYears", target = "age")
TargetBean sourceToTarget(SourceBean source);
@Mapping(source = "age", target = "ageYears")
SourceBean targetToSource(TargetBean target);
}
3. Mapping Nested Objects
@Mapper(uses = {AddressMapper.class})
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDto userToUserDto(User user);
}
4. Mapping Collections
@Mapper
public interface UserMapper {
List<UserDto> usersToUserDtos(List<User> users);
}
5. Custom Logic with @named
@Mapper
public abstract class OrderMapper {
@Named("centsToDollars")
public BigDecimal centsToDollars(Integer cents) {
if (cents == null) return null;
return new BigDecimal(cents).divide(new BigDecimal(100));
}
@Mapping(source = "priceInCents", target = "price", qualifiedByName = "centsToDollars")
public abstract OrderDto orderToOrderDto(Order order);
}
6. Default Values and Expressions
@Mapper
public interface ProductMapper {
@Mapping(target = "status", defaultValue = "AVAILABLE")
@Mapping(
target = "description",
expression = "java(product.getName().toUpperCase() + " - AWESOME!")"
)
ProductDto productToProductDto(Product product);
}
Best Practices
- Keep mappers focused
- Use
usesfor mapper composition - Prefer explicit
@Mapping
- Combine with Lombok
- Unit test custom logic
- For Spring:
@Mapper(componentModel = "spring")
Conclusion
MapStruct simplifies object mapping by generating type-safe, high-performance code at compile time.
Benefits include:
- Reduced boilerplate
- Compile-time safety
- Better maintainability
- Higher developer productivity
If you're not using MapStruct yet, give it a try!
Learn More
Check out the full guide and code examples in the GitHub repository:
➡️ Java-Mapping-With-Mapstruct
SOCIAL SHARE CARD GENERATOR