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.
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.
<code>@Mapper(componentModel = "spring")
public interface AreaMapping {
List<AreaInfoListVO> toVos(List<Area> areas);
}
</code>The above interface converts a list of PO objects to a list of VO objects with just a few lines.
<code>// spring bean
@Autowired
AreaMapping areaMapping;
// conversion source
List<Area> areas = …;
// conversion target
List<AreaInfoListVO> vos = areaMapping.toVos(areas);
</code>Writing such conversion manually would take a lot of time.
Converter
Spring provides a
Converter<S,T>interface:
<code>@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;
};
}
}
</code>It converts a source
Sto a target
T, which aligns with MapStruct’s purpose.
Converters are registered to
ConversionServicevia
ConverterRegistry, allowing you to invoke:
<code><T> T convert(@Nullable Object source, Class<T> targetType);
</code>MapStruct Spring Extensions
The official MapStruct Spring Extensions plugin makes any mapper that also implements
Converterautomatically register with
ConversionService:
<code>@Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
@Mapping(target = "seats", source = "seatConfiguration")
CarDto convert(Car car);
}
</code>Usage:
<code>@Autowired
private ConversionService conversionService;
Car car = …;
CarDto carDto = conversionService.convert(car, CarDto.class);
</code>The plugin generates an adapter class that registers the mapper:
<code>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);
}
}
</code>Custom Adapter Package and Name
By default the generated adapter resides in
org.mapstruct.extensions.spring.converterwith the name
ConversionServiceAdapter. You can change the package and class name:
<code>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 {
}
</code>Specifying a ConversionService Bean
If multiple
ConversionServicebeans exist, specify which one to use via
conversionServiceBeanName:
<code>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 {
}
</code>Integrating Spring’s Built‑in Converters
Spring provides many useful
Converter<S,T>implementations that are not directly exposed. Register them via
externalConversions:
<code>@MapperConfig(componentModel = "spring")
@SpringMapperConfig(
externalConversions = @ExternalConversion(sourceType = String.class, targetType = Locale.class))
public interface MapstructConfig {}
</code>The generated adapter will contain:
<code>@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);
}
}
</code>Summary
mapstruct-spring-annotations enables developers to use
ConversionServicewith defined MapStruct mappers without importing each mapper individually, allowing loose coupling between mappers while preserving MapStruct’s core mechanisms.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.