1. Introduction
MapStruct is a powerful library in Java that simplifies the process of mapping between different object models at compile time. It uses annotations to automatically generate type-safe mapper implementations, making it efficient and easy to maintain.
MapStruct is often used in applications that require object-to-object mapping, such as transferring data between layers or converting a DTO to an entity. A common use case is converting a String to a Date object, a process that can be tricky due to the numerous date formats and parsing requirements. In this article, we’ll explore various methods to achieve this conversion using MapStruct.
2. Dependencies
To start using MapStruct, we must include the necessary dependencies in our project. For the Maven project, we add the following dependency to our pom.xml:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
Additionally, we configure the maven-compiler-plugin to include the MapStruct processor:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
With these dependencies set up, we can begin using MapStruct in our project.
3. Basic Mapping With @Mapping Annotation
MapStruct provides the @Mapping annotation to define how properties in one type should be mapped to another. By default, MapStruct doesn’t support a direct conversion between String and Date. However, we can utilize the format attribute of the @Mapping annotation to handle the conversion.
Let’s see a basic example where we map a UserDto to a User entity:
public class UserDto {
private String name;
// Date in String format
private String birthDate;
// getters and setters
}
public class User {
private String name;
private Date birthDate;
// getters and setters
}
@Mapper
public interface UserMapper {
@Mapping(source = "birthDate", target = "birthDate", dateFormat = "yyyy-MM-dd")
User toUser(UserDto userDto);
}
In this example, the @Mapping annotation specifies that the birthDate field from UserDto should be mapped to the birthDate field in User. The dateFormat attribute is used to define the format of the date string, allowing MapStruct to handle the conversion automatically.
Let’s verify this conversion:
@Test
public void whenMappingUserDtoToUser_thenMapsBirthDateCorrectly() throws ParseException {
UserDto userDto = new UserDto();
userDto.setName("John Doe");
userDto.setBirthDate("2024-08-01");
User user = userMapper.toUser(userDto);
assertNotNull(user);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date expectedDate = dateFormat.parse("2024-08-01");
Assertions.assertEquals(expectedDate, user.getBirthDate());
}
In this test case, we confirm the accurate mapping of birthDate from a UserDto to a User using the UserMapper, confirming the expected date conversion.
4. Implementing Custom Conversion Methods
Sometimes, we might need to implement custom conversion methods for more complex scenarios. These methods can be defined directly within the mapper interface. By using the expression attribute of @Mapping annotation we can direct MapStruct to utilize our custom conversion methods.
Here’s how we can define a custom method to convert String to Date:
@Mapper
public interface UserMapper {
@Mapping(target = "birthDate", expression = "java(mapStringToDate(userDto.getBirthDate()))")
User toUserCustom(UserDto userDto) throws ParseException;
default Date mapStringToDate(String date) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.parse(date);
}
}
In this example, the mapStringToDate() method converts a String to a Date using SimpleDateFormat. We use an explicit expression in the @Mapping annotation to tell MapStruct to call this method during mapping.
5. Reuse General Custom Methods
Suppose we have multiple mappers in our project that require the same type of conversion. In that case, it’s more efficient to define the custom mapping methods in a separate utility class and reuse them across different mappers.
First, we’ll create a utility class with the conversion methods:
public class DateMapper {
public Date mapStringToDate(String date) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.parse(date);
}
}
Then, let’s use this utility class in our mapper:
@Mapper(uses = DateMapper.class)
public interface UserConversionMapper {
@Mapping(source = "birthDate", target = "birthDate")
User toUser(UserDto userDto);
}
By specifying uses = DateMapper.class in the @Mapper annotation, we tell MapStruct to use the methods in DateMapper for conversions. This approach promotes code reuse and keeps our mapping logic organized and maintainable.
6. Conclusion
MapStruct is a powerful tool for object-to-object mapping in Java. By leveraging custom methods, we can easily handle type conversions that are not supported out-of-the-box, such as converting String to Date.
By following the steps outlined in this article, we can efficiently implement and reuse custom conversion methods in our projects. Additionally, using the dateFormat attribute in the @Mapping annotation can simplify the process for straightforward date conversions.
These techniques allow us to harness the full potential of MapStruct for various mapping scenarios in our Java applications.
As always, the source code is available over on GitHub.