Preface

This is the reference documentation of the MapStruct Spring Extensions, an annotation processor designed to complement the core MapStruct project with features specific to the Spring Framework. This guide covers all the functionality provided by the MapStruct Spring Extensions. In case this guide doesn’t answer all your questions just join the MapStruct Google group to get help.

Please note that this guide assumes some familiarity with the MapStruct core. If this is your first introduction to MapStruct, you might wish to start with the core documentation.

You found a typo or other error in this guide? Please let us know by opening an issue in the MapStruct Spring Extensions GitHub repository, or, better yet, help the community and send a pull request for fixing it!

1. Introduction

The MapStruct Spring Extensions are a Java annotation processor extending the well known MapStruct project with features specific to the Spring Framework.

All you have to do is to define your MapStruct mapper to extend Spring’s Converter interface. During compilation, the extensions will generate an adapter which allows the standard MapStruct mappers to use Spring’s ConversionService.

This enables the developer to define MapStruct mappers with only the ConversionService in their uses attribute rather than having to import every single Mapper individually, thus allowing for looser coupling between Mappers.

2. Set up

MapStruct Spring Extensions is a Java annotation processor based on JSR 269 and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. Also, you will need MapStruct itself (at least version 1.4.0.Final) in your project.

It comprises the following artifacts:

  • org.mapstruct.extensions.spring:mapstruct-spring-annotations: contains the added annotations such as @SpringMapperConfig

  • org.mapstruct.extensions.spring:mapstruct-spring-extensions: contains the annotation processor which generates Spring components

2.1. Apache Maven

For Maven based projects add the following to your POM file in order to use MapStruct Spring Extensions:

Example 1. Maven configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
<properties>
    <org.mapstruct.extensions.spring.version>0.0.2</org.mapstruct.extensions.spring.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct.extensions.spring</groupId>
        <artifactId>mapstruct-spring-annotations</artifactId>
        <version>${org.mapstruct.extensions.spring.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct.extensions.spring</groupId>
                        <artifactId>mapstruct-spring-extensions</artifactId>
                        <version>${org.mapstruct.extensions.spring.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

If you are working with the Eclipse IDE, make sure to have a current version of the M2E plug-in. When importing a Maven project configured as shown above, it will set up the MapStruct Spring Extensions annotation processor so it runs right in the IDE, whenever you save a mapper type extending a Spring converter. Neat, isn’t it?

To double check that everything is working as expected, go to your project’s properties and select "Java Compiler" → "Annotation Processing" → "Factory Path". The MapStruct Spring Extensions JAR should be listed and enabled there. Any processor options configured via the compiler plug-in (see below) should be listed under "Java Compiler" → "Annotation Processing".

If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled. To do so, go to "Preferences" → "Maven" → "Annotation Processing" and select "Automatically configure JDT APT". Alternatively, specify the following in the properties section of your POM file: <m2e.apt.activation>jdt_apt</m2e.apt.activation>.

Also make sure that your project is using Java 1.8 or later (project properties → "Java Compiler" → "Compile Compliance Level"). It will not work with older versions.

2.2. Gradle

Add the following to your Gradle build file in order to enable MapStruct Spring Extensions:

Example 2. Gradle configuration (5.2 and later)
1
2
3
4
5
6
7
8
9
10
11
...

dependencies {
    ...
    implementation "org.mapstruct.extensions.spring:mapstruct-spring-annotations:${mapstructSpringExtensionsVersion}"
    annotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"

    // If you are using MapStruct Spring Extensions in test code
    testAnnotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"
}
...
Example 3. Gradle configuration (3.4 - 5.1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
plugins {
    ...
    id 'net.ltgt.apt' version '0.20'
}

// You can integrate with your IDEs.
// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
apply plugin: 'net.ltgt.apt-idea'
apply plugin: 'net.ltgt.apt-eclipse'

dependencies {
    ...
    implementation "org.mapstruct.extensions.spring:mapstruct-spring-annotations:${mapstructSpringExtensionsVersion}"
    annotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"

    // If you are using MapStruct Spring Extensions in test code
    testAnnotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"
}
...
Example 4. Gradle (3.3 and older)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
plugins {
    ...
    id 'net.ltgt.apt' version '0.20'
}

// You can integrate with your IDEs.
// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
apply plugin: 'net.ltgt.apt-idea'
apply plugin: 'net.ltgt.apt-eclipse'

dependencies {
    ...
    compile "org.mapstruct.extensions.spring:mapstruct-spring-annotations:${mapstructSpringExtensionsVersion}"
    annotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"

    // If you are using MapStruct Spring Extensions in test code
    testAnnotationProcessor "org.mapstruct.extensions.spring:mapstruct-spring-extensions:${mapstructSpringExtensionsVersion}"
}
...

2.3. Apache Ant

Add the javac task configured as follows to your build.xml file in order to enable MapStruct Spring Extensions in your Ant-based project. Adjust the paths as required for your project layout.

Example 5. Ant configuration
1
2
3
4
5
6
7
8
9
...
<javac
    srcdir="src/main/java"
    destdir="target/classes"
    classpath="path/to/mapstruct-spring-annotations0.0.2.jar">
    <compilerarg line="-processorpath path/to/mapstruct-spring-extensions-0.0.2.jar"/>
    <compilerarg line="-s target/generated-sources"/>
</javac>
...

3. Mappers as Converters

MapStruct Mappers nicely match Spring’s Converter idea:

1
2
3
4
5
@Mapper
public interface CarMapper extends Converter<Car, CarDto> {
    @Mapping(target = "seats", source = "seatConfiguration")
    CarDto convert(Car car);
}

This allows using the Mapper indirectly via the ConversionService:

1
2
3
4
5
6
...
    @Autowired
    private ConversionService conversionService;
...
    Car car = ...;
    CarDto carDto = conversionService.convert(car, CarDto.class);

All this can be achieved already with MapStruct’s core functionality. However, when a Mapper wants to invoke another one, it can’t take the route via the ConversionService, because the latter’s convert method does not match the signature that MapStruct expects for a mapping method. Thus, the developer still has to add every invoked Mapper to the invoking Mapper’s uses element. This creates (aside from a potentially long list) a tight coupling between Mappers that the ConversionService wants to avoid.

This is where MapStruct Spring Extensions can help. Including the two artifacts in your build will generate an Adapter class that can be used by an invoking Mapper. Let’s say that the above CarMapper is accompanied by a SeatConfigurationMapper:

1
2
3
4
5
6
@Mapper
public interface SeatConfigurationMapper extends Converter<SeatConfiguration, SeatConfigurationDto> {
    @Mapping(target = "seatCount", source = "numberOfSeats")
    @Mapping(target = "material", source = "seatMaterial")
    SeatConfigurationDto convert(SeatConfiguration seatConfiguration);
}

The generated Adapter class will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class ConversionServiceAdapter {
  private final ConversionService conversionService;

  public ConversionServiceAdapter(final ConversionService conversionService) {
    this.conversionService = conversionService;
  }

  public CarDto mapCarToCarDto(final Car source) {
    return conversionService.convert(source, CarDto.class);
  }

  public SeatConfigurationDto mapSeatConfigurationToSeatConfigurationDto(
      final SeatConfiguration source) {
    return conversionService.convert(source, SeatConfigurationDto.class);
  }
}----

Since this class' methods match the signature that MapStruct expects, we can now add it to the CarMapper:

1
2
3
4
5
@Mapper(uses = ConversionServiceAdapter.class)
public interface CarMapper extends Converter<Car, CarDto> {
    @Mapping(target = "seats", source = "seatConfiguration")
    CarDto convert(Car car);
}

3.1. Custom Names

By default, the generated class will be located in the package org.mapstruct.extensions.spring.converter and receive the name ConversionServiceAdapter. Typically, you will want to change these names, most often at least the package. This can be accomplished by adding the SpringMapperConfig annotation on any class within your regular source code. One natural candidate would be your shared configuration if you use this:

1
2
3
4
5
6
7
8
import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;
import org.mapstruct.extensions.spring.example.adapter.MyAdapter;

@MapperConfig(componentModel = "spring", uses = MyAdapter.class)
@SpringMapperConfig(conversionServiceAdapterPackage ="org.mapstruct.extensions.spring.example.adapter", conversionServiceAdapterClassName ="MyAdapter")
public interface MapperSpringConfig {
}

Note: If you do not specify the conversionServiceAdapterPackage element, the generated Adapter class will reside in the same package as the annotated Config.

3.2. Specifying The Conversion Service Bean Name

If your application has multiple ConversionService beans, you will need to specify the bean name. The SpringMapperConfig allows you to specify it using the conversionServiceBeanName property.

1
2
3
4
5
6
7
import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;

@MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class)
@SpringMapperConfig(conversionServiceBeanName = "myConversionService")
public interface MapperSpringConfig {
}