MapStruct: Simplify Java Object Mapping
— java — 3 min read
MapStruct is a code generator that generates mapping code at compile-time. It uses annotations to generate mapping code between Java beans. It generates code that performs mappings between source and target objects, including nested mappings, mappings between collections, and other advanced mapping scenarios.
It allows developers to write less boilerplate code while ensuring type-safe, null-safe, and easily maintainable mapping between different objects.
The MapStruct code generator can be used to generate mapping code for a variety of use cases, including:
- Mapping DTOs to entity classes
- Mapping between different versions of an API
- Converting data between different data models
- Implementing the Adapter Pattern
How MapStruct Works
MapStruct uses a combination of code generation and runtime mapping to generate mappings between different objects. It generates code that performs the mapping between source and target objects, eliminating the need for developers to write boilerplate code for mapping between objects.
To use MapStruct, developers need to define the mapping interfaces using annotations. These mapping interfaces are then compiled to generate the mapping code. The generated mapping code is then used at runtime to perform the mappings between objects.
Code Examples
To get started with MapStruct, you first need to include the MapStruct dependency in your Maven or Gradle build file. Once you have done that, you can start creating mapping interfaces.
Mapping Between DTO and Entity
Here is an example of a simple mapping interface that maps between two Person objects:
@Mapperpublic interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
PersonDTO personToPersonDTO(Person person);
Person personDTOToPerson(PersonDTO personDTO);}
In this example, we have defined a simple mapping interface that maps between a Person
object and a PersonDTO
object. The @Mapper
annotation tells MapStruct that this is a mapping interface that needs to be compiled to generate mapping code.
We have also defined two mapping methods, personToPersonDTO
and personDTOToPerson
, that perform the mapping between the Person
and PersonDTO
objects.
The INSTANCE
field is a static field that provides an instance of the generated mapping code. It is a good practice to define this field in all mapping interfaces.
Here is an example of how you can use the PersonMapper
interface to map between Person
and PersonDTO
objects:
Person person = new Person("John", "Doe");PersonDTO personDTO = PersonMapper.INSTANCE.personToPersonDTO(person);
In this example, we create a new Person
object and then use the personToPersonDTO
method of the PersonMapper
interface to map the Person
object to a PersonDTO
object.
Mapping Between Different Property Names
Suppose we have two classes, Person and User, with the following properties:
public class Person { private String name; private int age;
// constructors, getters, setters}
public class User { private String fullName; private int userAge;
// constructors, getters, setters}
To map between these two classes, we can create a MapStruct mapper interface with the appropriate mapping annotations:
@Mapperpublic interface PersonUserMapper { PersonUserMapper INSTANCE = Mappers.getMapper(PersonUserMapper.class);
@Mapping(source = "name", target = "fullName") @Mapping(source = "age", target = "userAge") User personToUser(Person person);}
In the mapper interface above, we are defining a method personToUser
that maps a Person
object to a User
object. We are using the @Mapping
annotation to specify which property from the source object (Person
) should be mapped to which property of the target object (User
). Here, we are mapping the name
property of Person
to the fullName
property of User
, and the age
property of Person
to the userAge
property of User
.
To use the mapper, we can simply call the personToUser
method on the INSTANCE
field of the PersonUserMapper
interface:
Person person = new Person("John Doe", 30);User user = PersonUserMapper.INSTANCE.personToUser(person);
This will create a new User
object with the fullName
and userAge
properties mapped from the corresponding properties of the Person
object.
Final Thoughts
In general, if you have complex mapping requirements or if you want to ensure that your mapping code is type-safe and maintainable, MapStruct is a good choice. However, if you have simple mapping requirements or if you are working with a small project, you may not need to use MapStruct.