VOOZH about

URL: https://www.javacodegeeks.com/handling-null-values-with-mapstruct.html

⇱ Handling Null Values with MapStruct - Java Code Geeks


MapStruct is a compile-time code generation framework that simplifies mapping between Java beans by eliminating boilerplate conversion code and relying on type-safe, generated implementations. One of the most common challenges in real-world applications is handling null values correctly while mapping objects—especially in scenarios involving partial updates, DTO-to-entity conversions, PATCH APIs, or evolving payload contracts. Let us delve into understanding MapStruct null values handling in Java and how it enables developers to build robust, predictable, and maintainable object mappings without resorting to manual null checks.

1. Overview

In enterprise Java applications, null handling is not merely a defensive coding concern but a core design consideration that directly impacts data integrity, API correctness, and maintainability. Improper null propagation can unintentionally overwrite persisted state, introduce subtle bugs, or force developers to add repetitive conditional logic across service layers. MapStruct Documentation addresses these challenges by offering declarative null-handling strategies that are enforced at compile time, ensuring both correctness and performance.

1.1 What is MapStruct?

MapStruct is a Java annotation processor that generates mapper implementations at compile time. It uses simple Java interfaces annotated with mapping annotations to automatically generate code that copies data between different object models, such as DTOs and entities. Unlike reflection-based mapping frameworks, MapStruct offers high performance, type safety, and clean separation of mapping logic without runtime overhead. By generating mapping code during compilation, it helps developers avoid writing repetitive boilerplate code and reduces the risk of runtime errors.

Through configuration options such as NullValuePropertyMappingStrategy, NullValueCheckStrategy, default values, and lifecycle hooks like @AfterMapping, MapStruct allows developers to precisely control how null values are treated during mapping. This makes it especially well-suited for modern architectures where DTOs often represent intent rather than complete state, and where partial updates are the norm rather than the exception.

2. Code Example

2.1 Maven Dependency

The following Maven configuration adds MapStruct and configures the compiler plugin to enable annotation processing so that mapper implementations are generated at compile time.

<dependencies>
 <dependency>
 <groupId>org.mapstruct</groupId>
 <artifactId>mapstruct</artifactId>
 <version>1.5.5.Final</version>
 </dependency>
</dependencies>

<build>
 <plugins>
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.11.0</version>
 <configuration>
 <source>17</source>
 <target>17</target>
 <annotationProcessorPaths>
 <path>
 <groupId>org.mapstruct</groupId>
 <artifactId>mapstruct-processor</artifactId>
 <version>1.5.5.Final</version>
 </path>
 </annotationProcessorPaths>
 </configuration>
 </plugin>
 </plugins>
</build>

The mapstruct dependency provides the core mapping annotations, while mapstruct-processor is registered under annotationProcessorPaths so the compiler can generate mapper implementations during the build; configuring the Java source and target versions ensures compatibility with modern language features and guarantees that mapping code is created at compile time rather than relying on reflection at runtime.

2.2 Model Classes

2.2.1 UserDto.java

This DTO represents the incoming user data, typically coming from an API request or external system, and intentionally allows null values to support partial updates.

public class UserDto {

 private String name;
 private String email;
 private Integer age;

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public Integer getAge() {
 return age;
 }

 public void setAge(Integer age) {
 this.age = age;
 }
}

The UserDto acts as a simple data carrier with no business logic, where nullable fields such as name, email, and age signal optional updates; this design is intentional so MapStruct can decide whether to propagate or ignore null values when mapping onto an existing entity.

2.2.2 UserEntity.java

This entity represents the persisted domain model and reflects the authoritative state stored in the database, which must be protected from accidental null overwrites.

public class UserEntity {

 private String name;
 private String email;
 private Integer age;
 private String status;

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public Integer getAge() {
 return age;
 }

 public void setAge(Integer age) {
 this.age = age;
 }

 public String getStatus() {
 return status;
 }

 public void setStatus(String status) {
 this.status = status;
 }
}

Unlike the DTO, the UserEntity models long-lived state and includes the derived status field, which is not directly provided by clients but instead computed during mapping; preserving existing values in this class is the primary motivation for applying MapStruct’s null-handling strategies.

2.3 Mapper Interface

This mapper defines how data flows from the DTO to the entity, with explicit configuration to control null handling and post-mapping business derivation.

import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserMapper {

 UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

 /**
 * Safe update: ignores null values from DTO, preserving existing entity values.
 */
 @BeanMapping(
 nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
 nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
 )
 void updateUserFromDto(UserDto dto, @MappingTarget UserEntity entity);

 /**
 * Unsafe update: null values overwrite existing entity fields.
 */
 @BeanMapping(
 nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL,
 nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
 )
 void unsafeUpdateUserFromDto(UserDto dto, @MappingTarget UserEntity entity);

 @AfterMapping
 default void afterMapping(@MappingTarget UserEntity entity) {
 if (entity.getAge() != null && entity.getAge() >= 18) {
 entity.setStatus("ADULT");
 } else {
 entity.setStatus("MINOR");
 }
 }
}

The UserMapper interface defines two mapping methods with different null handling strategies: updateUserFromDto performs a safe update by ignoring null values from the source DTO to preserve existing fields in the target entity, configured using NullValuePropertyMappingStrategy.IGNORE and always performing null checks with NullValueCheckStrategy.ALWAYS; in contrast, unsafeUpdateUserFromDto explicitly allows null values to overwrite the entity’s fields, using NullValuePropertyMappingStrategy.SET_TO_NULL to demonstrate the risk of unintended data loss in partial updates. Both methods are implemented by MapStruct at compile time through the Mappers.getMapper factory. Additionally, the @AfterMapping annotated method runs after mapping to set a derived status field based on the entity’s age, marking the user as “ADULT” if age is 18 or older, otherwise “MINOR”, thereby separating core mapping logic from business-specific post-processing.

2.4 Main Class

This simple driver class demonstrates how MapStruct applies null-handling rules during a real partial-update scenario at runtime.

public class MapStructDemo {

 public static void main(String[] args) {

 // Existing entity with initial data
 UserEntity entity = new UserEntity();
 entity.setName("John");
 entity.setEmail("john@example.com");
 entity.setAge(30);

 // Incoming DTO with nulls for name and age, email updated
 UserDto dto = new UserDto();
 dto.setName(null);
 dto.setEmail("john.new@example.com");
 dto.setAge(null);

 // Safe update: nulls ignored, existing values preserved
 UserMapper.INSTANCE.updateUserFromDto(dto, entity);
 System.out.println("Safe update:");
 printEntity(entity);

 // Reset entity for unsafe update
 entity.setName("John");
 entity.setEmail("john@example.com");
 entity.setAge(30);

 // Unsafe update: nulls overwrite fields (undesired)
 UserMapper.INSTANCE.unsafeUpdateUserFromDto(dto, entity);
 System.out.println("\nUnsafe update:");
 printEntity(entity);
 }

 private static void printEntity(UserEntity entity) {
 System.out.println("Name : " + entity.getName());
 System.out.println("Email : " + entity.getEmail());
 System.out.println("Age : " + entity.getAge());
 System.out.println("Status : " + entity.getStatus());
 }
}

The MapStructDemo class demonstrates two scenarios of mapping a UserDto onto an existing UserEntity. Initially, it creates an entity with preset values and a DTO with some fields set to null to simulate a partial update. Using the safe update method updateUserFromDto, the null fields in the DTO are ignored, preserving the entity’s original name and age, while updating the email. The entity state is printed showing these changes. Next, the entity is reset to its original state, and the unsafe update method unsafeUpdateUserFromDto is invoked, where null values in the DTO overwrite the entity’s fields, resulting in name and age becoming null, illustrating a common pitfall in partial updates. The corresponding entity state is printed again, reflecting these undesired null overwrites. Throughout, the printEntity helper method displays the current state of the entity, including the derived status field set by the mapper’s @AfterMapping logic based on the entity’s age.

2.5 Code Run and Output

The following output shows the final state of the entity after the mapping operation has been executed with null-handling rules applied.

Safe update:
Name : John
Email : john.new@example.com
Age : 30
Status : ADULT

Unsafe update:
Name : null
Email : john.new@example.com
Age : null
Status : MINOR

The output clearly contrasts the two mapping strategies: in the Safe update, the original name and age fields remain unchanged because null values from the DTO are ignored, while the email is successfully updated; the status is set to “ADULT” based on the preserved age. In the Unsafe update, the null values from the DTO overwrite the existing entity fields, causing name and age to become null, which in turn sets the status to “MINOR”. This demonstrates the importance of carefully configuring MapStruct’s null handling to prevent unintended data loss during partial updates.

3. Conclusion

Handling null values correctly is critical for building robust and predictable applications, and MapStruct provides multiple mechanisms—such as strategies, annotations, and lifecycle hooks—to manage nulls effectively without cluttering code with manual checks; key takeaways include using NullValuePropertyMappingStrategy.IGNORE for partial updates, leveraging @AfterMapping for derived or conditional logic, and applying defaultValue along with nullValueCheckStrategy for defensive mappings, all of which together make MapStruct a powerful, clean, and maintainable solution for object mapping in enterprise Java applications.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

👁 Photo of Yatin Batra
Yatin Batra
January 16th, 2026Last Updated: January 16th, 2026
0 704 6 minutes read

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button
Close
wpDiscuz