Baeldung

Java, Spring and Web Development tutorials

 

MapStruct Null Values Handling
2025-12-21 06:08 UTC by Emmanouil Varvarigos

1. Overview

MapStruct is a mapping library that allows engineers to handle complex mapping scenarios with minimal effort. One of MapStruct’s key features is default value assignment, which enables us to implement different population strategies for null values.

In this article, we’ll demonstrate ways to handle null values during object mapping by implementing mappers that leverage default value assignment and other MapStruct features.

2. The Models

Before we start writing and configuring mappers, we need the models that we’ll map in our examples. The source of our mappings will be the Order class:

public class Order {
    private String transactionId;
    private List<String> orderItemIds;
    private Payment payment;
    
    // getters - setters - constructors
}

Also, an Order object contains a Payment object as a property, so let’s define the Payment class:

public class Payment {
    private String type;
    private String amount;
    // getters - setters - constructors
}

Since we are focusing on null value handling, the target models are identical to the source models. The OrderDto is the Order counterpart:

public class OrderDto {
    private String transactionId;
    private List<String> orderItemIds;
    private PaymentDto payment;
    
    // getters - setters - constructors
}

Likewise, the PaymentDto is the mirror of the Payment class:

public class PaymentDto {
    private String type;
    private Double amount;
    // getters - setters - constructors
}

The PaymentMapper is a building block for most of the Order mappers, so it’s crucial to introduce it in this section, along with the other prerequisites:

@Mapper
public interface PaymentMapper {
    PaymentDto toDto(Payment payment);
}

The @Mapper annotation instructs the MapStruct processor to generate a class that implements the PaymentMapper interface. The MapStruct processing results can be found and inspected in the target folder of our project after compilation completes successfully.

3. The @AfterMapping Annotation

One way to change the mapper behaviour regarding null values is to utilize the AfterMapping annotation, which allows us to post-process a mapped object before the mapper method returns it. In general, a method annotated with the AfterMapping annotation may accept as arguments the mapping source, target, and context, so that the desired post-processing is applied. The OrderMapperWithAfterMapping showcases this approach, which is the most Java-centric:

@Mapper(uses = PaymentMapper.class)
public interface OrderMapperWithAfterMapping {
    OrderDto toDto(Order order);
    @AfterMapping
    default OrderDto postProcessing(@MappingTarget OrderDto orderDto) {
        if (orderDto.getOrderItemIds() == null) {
            orderDto.setOrderItemIds(new ArrayList<>());
        }
        if (orderDto.getTransactionId() == null) {
            orderDto.setTransactionId("N/A");
        }
        return orderDto;
    }
}

The MapStruct-generated toDto() method invokes the postProcessing() as the last step of the mapping process:

@Override
public OrderDto toDto(Order order) {
    if ( order == null ) {
        return null;
    }
    OrderDto orderDto = new OrderDto();
    orderDto.setPayment( paymentMapper.toDto( order.getPayment() ) );
    orderDto.setTransactionId( order.getTransactionId() );
    List list = order.getOrderItemIds();
    if ( list != null ) {
        orderDto.setOrderItemIds( new ArrayList( list ) );
    }
    OrderDto target = postProcessing( orderDto );
    if ( target != null ) {
        return target;
    }
    return orderDto;
}

Here, the postProcessing() method assigns an empty List to the orderItemIds property if it’s empty, and the “N/A” String to the transactionId property if it’s null.

4. The Mapping Annotation Capabilities

The Mapping annotation offers configurability for handling null values through the defaultValue and defaultExpression properties. Indeed, the OrderMapperWithDefault utilizes both:

@Mapper(uses = PaymentMapper.class)
public interface OrderMapperWithDefault {
    @Mapping(
            source = "payment",
            target = "payment",
            defaultExpression = "java(new com.baeldung.dto.PaymentDto())"
    )
    @Mapping(
            source = "transactionId", 
            target = "transactionId", 
            defaultValue = "N/A"
    )
    OrderDto toDto(Order order);
}

After the code generation, the result is a toDto() function that handles null values as instructed by the annotations:

@Override
public OrderDto toDto(Order order) {
    if ( order == null ) {
        return null;
    }
    OrderDto orderDto = new OrderDto();
    if ( order.getPayment() != null ) {
        orderDto.setPayment( paymentMapper.toDto( order.getPayment() ) );
    }
    else {
        orderDto.setPayment( new com.baeldung.dto.PaymentDto() );
    }
    if ( order.getTransactionId() != null ) {
        orderDto.setTransactionId( order.getTransactionId() );
    }
    else {
        orderDto.setTransactionId( "N/A" );
    }
    List list = order.getOrderItemIds();
    if ( list != null ) {
        orderDto.setOrderItemIds( new ArrayList( list ) );
    }
    return orderDto;
}

As instructed by the annotation configuration, the resulting MapStruct implementation initializes the value of the payment or transactionId property in case the value is null.

5. Null Checks

To understand why and when null checks are applied, we need to describe how mapping methods handle their arguments first. By default, mapping methods invoke the subsequent mapping functions for each object property without checking if a source property is null. This works because the generated mapping methods check if their argument is null beforehand.

To change this behaviour, we need to configure the nullValueCheckStrategy option. The default value of the nullValueCheckStrategy option is ON_IMPLICIT_CONVERSIONS. Methods annotated with the ON_IMPLICIT_CONVERSIONS value introduce null checks only for properties that are mapped directly or via a type conversion.

The PaymentMapperImpl illustrates how null checks work when mappers perform type conversions:

public class PaymentMapperImpl implements PaymentMapper {
    public PaymentDto toDto(Payment payment) {
        if (payment == null) {
            return null;
        } else {
            PaymentDto paymentDto = new PaymentDto();
            paymentDto.setType(payment.getType());
            if (payment.getAmount() != null) {
                paymentDto.setAmount(Double.parseDouble(payment.getAmount()));
            }
            return paymentDto;
        }
    }
}

Indeed, the mapping method checks if the amount source property is null, so as not to throw a NullPointerException. To enforce null checks for every property, we need to set the ALWAYS value to the nullValueCheckStrategy option. To demonstrate this configuration, let’s write the AlwaysNullCheckPaymentMapper:

@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface AlwaysNullCheckPaymentMapper {
    PaymentDto toDto(Payment payment);
}

The only difference between the PaymentMapper and the AlwaysNullCheckPaymentMapper is the nullValueCheckStrategy setting. The generated AlwaysNullCheckPaymentMapperImpl checks the type property for a null value in addition to the amount property:

public class AlwaysNullCheckPaymentMapperImpl implements AlwaysNullCheckPaymentMapper {
    public PaymentDto toDto(Payment payment) {
        if (payment == null) {
            return null;
        } else {
            PaymentDto paymentDto = new PaymentDto();
            if (payment.getType() != null) {
                paymentDto.setType(payment.getType());
            }
            if (payment.getAmount() != null) {
                paymentDto.setAmount(Double.parseDouble(payment.getAmount()));
            }
            return paymentDto;
        }
    }
}

6. Conclusion

In this article, we demonstrated different approaches to handling null values with MapStruct. Moreover, we dived deeper into the configuration that MapStruct annotations expose, to illustrate the flexibility of the library.

As always, the code is available over on GitHub.

The post MapStruct Null Values Handling first appeared on Baeldung.
       

 

Content mobilized by FeedBlitz RSS Services, the premium FeedBurner alternative.