MapStruct

Apr 7, 2025    m. Apr 7, 2025    #java   #mapstruct  

MapStruct is a powerful mapping library in Java. However, some of its behaviors can be non-obvious, especially when it comes to null handling and configuration.

Below are some common areas MapStruct users should be aware of.

Null Checks

Official documentation: Null Check Strategies .

NullValueCheckStrategy.ON_IMPLICIT_CONVERSION

The default NullValueCheckStrategy is ON_IMPLICIT_CONVERSION. This means MapStruct will insert a null check only if a type conversion is involved—e.g., mapping an Integer to a String.

// With type conversion
// Given input.count is an Integer and output.count is a String,
// a null check would be performed first.
if (input.getCount() != null) {
    output.setCount(String.valueOf(input.getCount()));
}

// Without type conversion
// When both input.name and output.name are Strings, it maps directly.
output.setName(input.getName());

NullValueCheckStrategy.ALWAYS

With NullValueCheckStrategy.ALWAYS, MapStruct always adds a null check for non-primitive source properties—unless a presence checker is defined on the source bean.

However, this strategy comes with a few important caveats:

  1. qualifiedByName() method won’t be called if the input is null

    public abstract class MovieMapper {
    
        @Mapping(target = "category", source = "movie", qualifiedByName = "categoryToString")
        public abstract GermanRelease toGerman(Movie movie);
    
        @Named("categoryToString")
        public String categoryToString(Movie movie) {
            // Some mapping logic
        }
    }
    
    // Generated code:
    if (movie != null) {
        output.setCategory(categoryToString(movie));
    }
    

    If movie is null, the categoryToString method is not invoked.

  2. expression mappings are not affected

    public abstract class MovieMapper {
    
        @Mapping(target = "category", expression = "java(movie.getCategory() != null ? movie.getCategory() : null)")
        public abstract GermanRelease toGerman(Movie movie);
    
        protected String categoryToString(Movie movie) {
            // Some mapping logic
        }
    }
    
    // Generated code:
    output.setCategory(movie.getCategory() != null ? movie.getCategory() : null);
    

    Expression-based mappings are inlined directly and aren’t skipped based on null checks for the source.

Deprecation of org.mapstruct.ap.spi.BuilderProvider

The SPI configuration via org.mapstruct.ap.spi.BuilderProvider (under META-INF/services) has stopped working since MapStruct 1.5.5.Final.

Refer to mapstruct#1661 and commit 5f4d3558 for details.

To disable builder usage, use the processor option mapstruct.disableBuilders during compilation.

A Stack Overflow answer shows how to do this with Gradle. Here’s how you can do it with Maven:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <compilerArgs>
            <compilerArg>
                -Amapstruct.disableBuilders=true <!-- Disable builder by default for Lombok compatibility -->
            </compilerArg>
        </compilerArgs>
    </configuration>
</plugin>

This can help avoid compatibility issues with libraries like Lombok.

Tip: Instead of disabling builders globally, consider disabling them at the mapper level for more transparency and control.