1. Introduction

When creating REST controllers, we usually do not expose our entities, but instead, create classes that contain only the necessary set of attributes. In between, we do some mappings, where MapStruct could be very helpful and spare us a lot of hours. Sometimes, it's necessary to create custom mappers since we have to apply some business logic during the mapping or we have to map one data type to another. In this tutorial, we'll show how to create a custom MapStruct mapper that will map one type to another - OffsetDateTime to Timestamp and vice versa.

2. Implementation

First, we need to have MapStruct Maven dependency in our pom.xml file:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.0.CR1</version>
</dependency>

Let's say that we have an entity called Purchase that represents the purchase of a product by a customer on a specific time.

So, in this example we'll have:

  • Purchase.java - domain class
  • PurchaseDto.java - Dto class
  • DateTimeMapper.java - Custom MapStruct mapper that will convert java.time.OffsetDateTime to java.sql.Timestamp and vice versa.
  • PurchaseMapper.java - here we'll see how to make use of custom mapper

Purchase.java

@Entity
public class Purchase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    Long itemId;
    Long customerId;
    Timestamp purchaseDateTime;
    
    // getters, setters, etc
}

PurchaseDto.java

public class PurchaseDto {

    private OffsetDateTime purchaseDateTime;
    private Long itemId;

}

DateTimeMapper.java

import org.springframework.stereotype.Component;
import java.sql.Timestamp;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

@Component
public class DateTimeMapper {
    public OffsetDateTime toOffsetDateTime(Timestamp timestamp){
        if (timestamp != null){
          return OffsetDateTime.of(timestamp.toLocalDateTime().getYear(), 
             timestamp.toLocalDateTime().getMonthValue(),
             timestamp.toLocalDateTime().getDayOfMonth(), 
             timestamp.toLocalDateTime().getHour(), 
             timestamp.toLocalDateTime().getMinute(),
             timestamp.toLocalDateTime().getSecond(), 
             timestamp.toLocalDateTime().getNano(), 
             ZoneOffset.UTC);
        } else {
            return null;
        }
    }

    public Timestamp toTimestamp(OffsetDateTime offsetDateTime){
        if(offsetDateTime != null) {
            return Timestamp.valueOf(offsetDateTime.atZoneSameInstant(ZoneOffset.UTC)
              .toLocalDateTime());
        } else {
            return null;
        }
    }
}

PurchaseMapper.java

@Mapper(uses = { DateTimeMapper.class })
public interface PurchaseMapper {
    PurchaseDto purchaseToPurchaseDto(Purchase purchase);
    Purchase purchaseDtoToPurchase(PurchaseDto purchaseDto);
}

Notice that in @Mapper annotation we have uses keyword followed by the list of custom mappers. This is how our DateTimeMapper class is being injected to be used for conversion between Purchase and PurchaseDto.

3. Conclusion

In this short tutorial, we showed how to create a custom mapper with MapStruct. Complete MapStruct documentation could be found here.