MapStruct QualifiedByName vs Expression in Spring Boot

MapStruct is a popular library for mapping DTOs(Data Transfer Objects) to Entities and vice versa in a Spring Boot application. When dealing with custom mapping and type conversions, MapStruct provides two primary attributes: qualifiedByName with @Mapping and an expression with @Mapping. Each attribute has specific strengths, ideal use cases, and limitations. This topic will help us understand the MapStruct qualifiedByName vs expression attributes of the @Mapping annotation of the MapStruct in a Spring Boot application and provide insight into when to use each for our mapping needs.

Comparison of MapStruct QualifiedByName vs Expression in Spring Boot applications

What is QualifiedByName in MapStruct?

The qualifiedByName attribute in the @Mapping annotation in MapStruct is used to identify and apply custom mapping methods by name. It allows us to define reusable helper methods that can be applied to specific fields across multiple mappings.

Key Characteristics of QualifiedByName:

  • Reusability: With this methods are reusable across multiple mappings, making it easy to use the same logic in different places.
  • Type-Safe: Using this attribute methods are defined in the Java class, they provide compile-time safety.
  • Better for Complex Logic: Ideal for transformations that are too complex to fit inline but are necessary across different fields or mappings.

What is Expression in MapStruct?

An Expression is an attribute in MapStruct’s @Mapping annotation that allows us to write inline Java code directly within the mapping. It is particularly useful for quick, one-off transformations where defining a separate helper method might not be necessary.

Key Characteristics of Expression:

  • In-line Code: The Java expression is embedded directly within the mapping annotation, making it convenient for simple, quick transformations.
  • Single-Use: Typically used for one-off transformations specific to a single mapping.
  • No Reusability: Expressions are specific to the mapping they are defined in, making them less reusable than qualifiedByName.

Example Use Case for MapStruct QualifiedByName vs Expression

Let’s consider a scenario where we have a WebSeries entity and WebSeriesDTO. We want to map between these classes while handling custom type conversions using both QualifiedByName and Expression.

Entity: WebSeries

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Data
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int release;
}

DTO: WebSeriesDTO

import lombok.Data;

@Data
public class WebSeriesDTO {
private Long id;
private String name;
private String release;
}

Using QualifiedByName for Reusable Conversions

Let’s consider that we need to convert the String type DTO’s release field to the Int type Entity’s release field and vice versa. Since these conversions might require us to use them in multiple places, instead of defining them as separate,we can reuse these methods by using the qualifiedByName attribute of MapStruct.

Use QualifiedByName in the Mapper

We need to create an Interface with the @Mapper annotation and then convert helper methods annotated with the @Name annotation. After that, we need to define mapping methods and use the qualifiedByName attribute of the @Mapping annotation.

Mapper Interface: WebSeriesMapper

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;

@Mapper
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);

//Custom method to map int to String
@Named("mapIntToString")
default String mapStatusToBoolean(int release) {
return release == 1 ? "yes": "no";
}
//Custom method to map String to int
@Named("mapStringToInt")
default int mapStatusToInt(String release) {
return release.equals("yes") ? 1 : 0;
}
//Map Entity to DTO with custom type handling 
@Mapping(source = "release", target = "release", qualifiedByName = "mapIntToString")
WebSeriesDTO entityToDto(WebSeries entity);

//Map DTO to Entity with custom type handling 
@Mapping(source = "release", target = "release", qualifiedByName = "mapStringToInt")
WebSeries dtoToEntity(WebSeriesDTO dto);
}

Using Expression for One-Off Transformations

For a single-use conversion, such as converting the release field from boolean to int and vice versa, the expression attribute of MapStruct is more convenient. Let’s consider the example below:

Entity: WebSeries

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Data
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int release;
}

DTO: WebSeriesDTO

import lombok.Data;

@Data
public class WebSeriesDTO {
private Long id;
private String name;
private boolean release;
}

Using Expression in Mapper

We are using the expression attribute of MapStruct by using @Mapping annotation in the Mapper interface.

Mapper Interface: WebSeriesMapper

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);

//Map Entity to DTO with custom type handling 
@Mapping(target = "release", expression = "java(entity.getRelease()==1 ? true : false)")
WebSeriesDTO entityToDto(WebSeries entity);

//Map DTO to Entity with custom type handling 
@Mapping(target = "release", expression = "java(dto.isRelease() ? 1 : 0)")
WebSeries dtoToEntity(WebSeriesDTO dto);
}

Comparison: QualifiedByName vs Expression

FeatureQualifiedByNameExpression
Use CaseComplex, reusable conversionsSimple, single-use transformations
ReusabilityHigh – Can be applied across multiple mappingsLow – Embedded directly in a single mapping
Type SafetyHigh – Uses type-safe, named methodsModerate – Java code is embedded directly
Complex LogicSuitable for detailed or complex logicLimited to simple logic
Code MaintenanceCentralized in reusable methodsHarder to maintain if repeated in multiple places
PerformanceBetter for repeated logic due to compile-time resolutionSlightly slower, evaluated within each mapping
Comparison of MapStruct QualifiedByName vs Expression in Spring Boot applications

When to Use Each Approach

  • Use the qualifiedByName attribute  for reusable, complex mappings that can be applied to multiple fields or mapping interfaces. It keeps your code modular and easier to maintain.
  • Use expression attribute for quick, one-off transformations that don’t justify a dedicated helper method. This is ideal for fields that require simple inline logic without being reused elsewhere.

Conclusion

Both QualifiedByName and Expression attributes have their unique advantages in MapStruct:

  • The qualifiedByName attribute is excellent for modular, reusable mapping methods, ensuring clean code and easy maintenance.
  • The expression attribute works well for straightforward, one-off transformations where the mapping logic is concise.

Leave a Comment