eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

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.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI (cat=Java)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)