Was ist MapStruct?
MapStruct ist ein Annotation-basierter Codegenerator zur Abbildung (Mapping) von Java-Beans. Ziel ist es, wiederkehrende und fehleranfällige Konvertierungslogik – etwa zwischen Entitäten und DTOs – deklarativ zu beschreiben und den eigentlichen Mapping-Code zur Compile-Zeit generieren zu lassen.
Im Gegensatz zu Reflection-basierten Frameworks wie ModelMapper oder Dozer erzeugt MapStruct reinen Java-Code ohne Laufzeit-Overhead. Dadurch ist es:
- Performant (keine Reflection)
- Typsicher (Fehler fallen beim Kompilieren auf)
- Debug-freundlich (generierter Code ist lesbar)
Typische Einsatzszenarien:
- Entity ↔ DTO Mapping
- API-Modelle ↔ Domänenmodelle
- Persistenzschicht ↔ Business-Schicht
Grundprinzip von MapStruct
Ein Mapper wird als Interface definiert und mit @Mapper annotiert:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDto toDto(User user);
User toEntity(UserDto dto);
}
MapStruct generiert zur Compile-Zeit eine Implementierung wie UserMapperImpl.
Felder mit gleichem Namen und kompatiblem Typ werden automatisch gemappt. Abweichungen können mit @Mapping konfiguriert werden:
@Mapping(source = "firstName", target = "givenName")
UserDto toDto(User user);
Code-Sprache: CSS (css)
Collections in MapStruct
In realen Anwendungen werden selten einzelne Objekte gemappt – häufig sind es Listen oder Sets.
Ein einfaches Beispiel:
List<UserDto> toDtoList(List<User> users);
Code-Sprache: HTML, XML (xml)
MapStruct erkennt automatisch:
- Es existiert eine
toDto(User user)-Methode - Also wird diese für jedes Element der Liste verwendet
Der generierte Code sieht sinngemäß so aus:
List<UserDto> list = new ArrayList<>(users.size());
for (User user : users) {
list.add(toDto(user));
}
Code-Sprache: PHP (php)
Doch was passiert, wenn:
- mehrere mögliche Mapping-Methoden existieren?
- spezielle Qualifier verwendet werden?
- Null-Werte behandelt werden sollen?
- ein anderes Collection-Format erzeugt werden soll?
Hier kommt @IterableMapping ins Spiel.
@IterableMapping – Kontrolle über Collection-Mappings
@IterableMapping erlaubt es, das Verhalten beim Mapping von Iterables (List, Set, Collection etc.) gezielt zu konfigurieren.
Beispiel:
@IterableMapping(qualifiedByName = "lightweight")
List<UserDto> toDtoList(List<User> users);
Code-Sprache: CSS (css)
Diese Annotation wird verwendet, wenn:
- Mehrere Mapping-Methoden für denselben Typ existieren
- Eine bestimmte Mapping-Variante erzwungen werden soll
- Formatierungen (z. B. bei Datumswerten) notwendig sind
- Null-Handling gesteuert werden soll
Beispiel: Mehrere Mapping-Varianten
Angenommen, es gibt zwei Varianten:
@Named("detailed")
UserDto toDetailedDto(User user);
@Named("lightweight")
UserDto toLightweightDto(User user);
Code-Sprache: CSS (css)
Nun kann explizit gesteuert werden, welche Methode für eine Liste verwendet wird:
@IterableMapping(qualifiedByName = "lightweight")
List<UserDto> toLightweightDtoList(List<User> users);
Code-Sprache: CSS (css)
Ohne @IterableMapping würde MapStruct einen Ambiguitätsfehler erzeugen.
Null-Handling mit @IterableMapping
MapStruct bietet verschiedene Strategien für Null-Werte.
@IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
List<UserDto> toDtoList(List<User> users);
Code-Sprache: HTML, XML (xml)
Optionen:
RETURN_NULL(Standard)RETURN_DEFAULT→ leere Liste stattnull
Gerade in REST-APIs ist RETURN_DEFAULT häufig sinnvoll, um NullPointerExceptions zu vermeiden.
Datumsformatierung in Collections
Auch Formatierungen können über @IterableMapping konfiguriert werden:
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> mapDates(List<Date> dates);
Code-Sprache: JavaScript (javascript)
Hier wird jedes Date-Element mit dem angegebenen Format in einen String konvertiert.
ElementTargetType festlegen
Wenn der Zieltyp nicht eindeutig ist, kann elementTargetType helfen:
@IterableMapping(elementTargetType = SpecialUserDto.class)
List<UserDto> mapUsers(List<User> users);
Code-Sprache: HTML, XML (xml)
Dies ist insbesondere bei Vererbungshierarchien nützlich.
Zusammenspiel mit @Mapping und @BeanMapping
Wichtig zu verstehen:
@Mapping→ Konfiguration einzelner Felder@BeanMapping→ Konfiguration für Objekt-Mappings@IterableMapping→ Konfiguration für Collection-Mappings
Beispiel:
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
@Mapping(target = "name", source = "name")
UserDto toSlimDto(User user);
@IterableMapping(qualifiedByName = "toSlimDto")
List<UserDto> toSlimDtoList(List<User> users);
Code-Sprache: CSS (css)
So lassen sich sehr gezielt unterschiedliche Projektionen definieren.
Performance-Aspekte
Da MapStruct reinen Java-Code generiert:
- Keine Reflection
- Keine Proxy-Objekte
- Keine Laufzeit-Analyse
Das macht es besonders attraktiv in:
- Microservices
- Hochperformanten REST-APIs
- Batch-Verarbeitung großer Datenmengen
Gerade bei großen Collections ist dies ein entscheidender Vorteil gegenüber dynamischen Mappern.
Best Practices für @IterableMapping
- Explizit qualifizieren, wenn mehrere Mapping-Methoden existieren
- NullValueMappingStrategy bewusst setzen
- DTO-Varianten sauber benennen
- Collection-Mapping-Methoden klar von Einzel-Mappings trennen
- Keine unnötigen Konfigurationen – MapStruct arbeitet konventionsbasiert
Fazit
MapStruct bietet eine elegante, performante und typsichere Lösung zur Objektabbildung in Java. Während einfache Collection-Mappings automatisch funktionieren, ermöglicht @IterableMapping eine präzise Steuerung bei komplexeren Anforderungen.
Besonders in größeren Projekten mit:
- mehreren DTO-Varianten
- differenzierten API-Projektionen
- Vererbungsstrukturen
- spezifischem Null-Handling
ist @IterableMapping ein zentrales Werkzeug, um Mapping-Logik sauber, deklarativ und wartbar zu halten.
Wer strukturierte, explizite und compile-time geprüfte Mappings bevorzugt, findet in MapStruct eine robuste Alternative zu Reflection-basierten Lösungen – mit klaren Vorteilen in Performance und Wartbarkeit.