Boost Spring Apps with MapStruct: Seamless Conversion Service Integration

This article explains how to replace manual BeanUtil mapping with MapStruct, leverage Spring's Converter interface, and use the MapStruct Spring Extensions plugin to automatically register mappers with ConversionService, including custom adapter configuration and integration of built‑in Spring converters.

macrozheng
macrozheng
macrozheng
Boost Spring Apps with MapStruct: Seamless Conversion Service Integration
Previously I recommended the MapStruct tool, which can replace BeanUtil for converting between DTO, VO, and PO objects using Java's compile‑time annotation processor.

MapStruct generates clean code, dramatically reducing manual mapping effort.

@Mapper(componentModel = "spring")
public interface AreaMapping {
    List<AreaInfoListVO> toVos(List<Area> areas);
}

The above interface converts a list of PO objects to a list of VO objects with just a few lines.

// spring bean
@Autowired
AreaMapping areaMapping;

// conversion source
List<Area> areas = …;
// conversion target
List<AreaInfoListVO> vos = areaMapping.toVos(areas);

Writing such conversion manually would take a lot of time.

Converter

Spring provides a Converter<S,T> interface:

@FunctionalInterface
public interface Converter<S,T> {
    @Nullable
    T convert(S source);

    default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
        Assert.notNull(after, "After Converter must not be null");
        return (s) -> {
            T initialResult = this.convert(s);
            return initialResult != null ? after.convert(initialResult) : null;
        };
    }
}

It converts a source S to a target T, which aligns with MapStruct’s purpose.

Converters are registered to ConversionService via ConverterRegistry, allowing you to invoke:

<T> T convert(@Nullable Object source, Class<T> targetType);

MapStruct Spring Extensions

The official MapStruct Spring Extensions plugin makes any mapper that also implements Converter automatically register with ConversionService:

@Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
    @Mapping(target = "seats", source = "seatConfiguration")
    CarDto convert(Car car);
}

Usage:

@Autowired
private ConversionService conversionService;

Car car = …;
CarDto carDto = conversionService.convert(car, CarDto.class);

The plugin generates an adapter class that registers the mapper:

package org.mapstruct.extensions.spring.converter;

import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDto;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;

@Component
public class ConversionServiceAdapter {
    private final ConversionService conversionService;

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

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

Custom Adapter Package and Name

By default the generated adapter resides in org.mapstruct.extensions.spring.converter with the name ConversionServiceAdapter. You can change the package and class name:

package cn.felord.mapstruct.config;

import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;

@MapperConfig(componentModel = "spring")
@SpringMapperConfig(conversionServiceAdapterPackage = "cn.felord.mapstruct.config",
        conversionServiceAdapterClassName = "MapStructConversionServiceAdapter")
public class MapperSpringConfig {
}

Specifying a ConversionService Bean

If multiple ConversionService beans exist, specify which one to use via conversionServiceBeanName:

package cn.felord.mapstruct.config;

import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;

@MapperConfig(componentModel = "spring")
@SpringMapperConfig(conversionServiceAdapterPackage = "cn.felord.mapstruct.config",
        conversionServiceAdapterClassName = "MapStructConversionServiceAdapter",
        conversionServiceBeanName = "myConversionService")
public class MapperSpringConfig {
}

Integrating Spring’s Built‑in Converters

Spring provides many useful Converter<S,T> implementations that are not directly exposed. Register them via externalConversions:

@MapperConfig(componentModel = "spring")
@SpringMapperConfig(
   externalConversions = @ExternalConversion(sourceType = String.class, targetType = Locale.class))
public interface MapstructConfig {}

The generated adapter will contain:

@Component
public class ConversionServiceAdapter {
    private final ConversionService conversionService;

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

    public Locale mapStringToLocale(final String source) {
        return conversionService.convert(source, Locale.class);
    }
}

Summary

mapstruct-spring-annotations enables developers to use ConversionService with defined MapStruct mappers without importing each mapper individually, allowing loose coupling between mappers while preserving MapStruct’s core mechanisms.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaCode GenerationBackend DevelopmentspringmapstructConversionService
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.