Understanding MapStruct @IterableMapping
MapStruct is a popular Java annotation processor that generates type-safe and high-performance mapping code at compile time. It is widely used to map between DTOs and entities in layered architectures. When working with collections such as List, Set, or arrays, MapStruct provides powerful features to customize how elements inside those collections are mapped. One of the most important annotations for this purpose is @IterableMapping. Let us delve into understanding Java MapStruct @IterableMapping and how it enables precise and efficient mapping of collection-based data structures in Java applications.
1. What is @IterableMapping annotation?
@IterableMapping is a MapStruct annotation used to customize and control the mapping behavior of iterable types such as List, Set, Iterable, and arrays. It is especially useful when converting collections whose elements require specific mapping logic rather than relying on MapStruct’s default behavior. By applying this annotation, developers can explicitly define how each element within a collection should be transformed from a source type to a target type. The @IterableMapping annotation allows you to:
- Specify the exact element-mapping method that should be applied to each item in the collection, ensuring consistent and predictable transformations
- Apply qualifiers such as
@Namedor custom@Qualifierannotations to select the appropriate mapping method when multiple options are available - Define how
nullsource collections should be handled, for example by returning an empty collection instead ofnull - Explicitly control the target element type, which is helpful when mapping between different collection implementations or abstract types
Without @IterableMapping, MapStruct automatically applies its default element-mapping strategy, which may not always meet application-specific requirements. By using @IterableMapping, you gain fine-grained control over collection transformations, improve null safety, and ensure that complex or customized mapping logic is applied consistently across all elements in a collection.
2. Code Example
2.1 Adding Project Dependency
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>stable__jar__version</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>stable__jar__version</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
This Maven configuration adds MapStruct to the project and enables its annotation processing during compilation. The mapstruct dependency provides the core API used to define mapper interfaces, while the maven-compiler-plugin is configured to run the MapStruct annotation processor at build time. By specifying the Java source and target version as 17, the project ensures compatibility with modern language features. The annotationProcessorPaths section explicitly registers mapstruct-processor, which is responsible for generating the mapper implementation classes during compilation, allowing MapStruct to produce fast, type-safe mapping code without using reflection at runtime.
2.2 Adding the Entity Class
public class UserEntity {
private Long id;
private String name;
private String email;
// getters and setters
}
The UserEntity class represents the domain or persistence-layer model and typically corresponds to a database table. It contains core user attributes such as id, name, and email, where the email field may include sensitive or internal data required by the application but not intended to be exposed externally. This class is commonly used within the data access and business layers and serves as the source object when mapping to a DTO using MapStruct.
2.3 Adding the DTO Class
public class UserDTO {
private Long id;
private String name;
// getters and setters
}
The UserDTO class is a Data Transfer Object designed to carry only the necessary user information across application boundaries, such as REST APIs or service layers. It includes a subset of fields from the entity, namely id and name, intentionally excluding fields like email to reduce payload size and prevent accidental exposure of sensitive data. When used with MapStruct, this DTO acts as the target object for mapping operations, ensuring a clean separation between internal models and external representations.
2.4 Mapper Interface (Using @IterableMapping)
import org.mapstruct.IterableMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Named("entityToDto")
UserDTO toDto(UserEntity entity);
@IterableMapping(
qualifiedByName = "entityToDto",
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_EMPTY
)
List<UserDTO> toDtoList(List<UserEntity> entities);
}
The UserMapper interface defines the mapping logic between UserEntity and UserDTO using MapStruct’s annotation-based approach. The @Mapper annotation instructs MapStruct to generate an implementation of this interface at compile time, while the INSTANCE field uses Mappers.getMapper() to obtain a singleton mapper instance. The toDto() method is annotated with @Named to explicitly identify it as the element-mapping method, which is then referenced by @IterableMapping in the toDtoList() method to ensure that each UserEntity in the source list is converted using this specific mapping logic. Additionally, the nullValueMappingStrategy set to RETURN_EMPTY guarantees that an empty list is returned when the source collection is null, providing safer and more predictable collection handling.
2.5 Main Class (Run This)
import java.util.List;
public class MainApp {
public static void main(String[] args) {
UserEntity user1 = new UserEntity();
user1.setId(1L);
user1.setName("Alice");
user1.setEmail("alice@example.com");
UserEntity user2 = new UserEntity();
user2.setId(2L);
user2.setName("Bob");
user2.setEmail("bob@example.com");
List<UserEntity> entities = List.of(user1, user2);
List<UserDTO> dtos = UserMapper.INSTANCE.toDtoList(entities);
dtos.forEach(dto ->
System.out.println(dto.getId() + " - " + dto.getName())
);
}
}
The MainApp class demonstrates how the MapStruct mapper is used at runtime by creating sample UserEntity objects, populating them with test data, and storing them in a list. This list is then passed to the toDtoList() method of UserMapper, which converts each entity into a UserDTO using the mapping rules defined earlier. The resulting DTO list is iterated over and printed to the console, showing that only the mapped fields (id and name) are included in the output, thereby validating the correct behavior of @IterableMapping and the element-level mapping configuration.
2.6 Code Run and Output
When the application runs, the main() method creates two UserEntity objects with populated id, name, and email values and stores them in a list. This list is passed to the MapStruct-generated toDtoList() method, which iterates over each entity and invokes the toDto() mapping method defined in UserMapper. During this process, only the fields present in UserDTO are copied, while unmapped fields such as email are intentionally ignored. As a result, the console output displays one line per mapped object in the format id - name, producing the values 1 - Alice and 2 - Bob, which confirms that the collection mapping and element-level conversion using @IterableMapping are working as expected.
3. Conclusion
@IterableMapping is a powerful MapStruct feature that gives you precise control over how collections are mapped. It is especially useful when you need to select a specific element-mapping method, want predictable behavior for null collections, or work with multiple DTO representations. By combining @IterableMapping with qualifiers and null-handling strategies, you can create clean, safe, and highly maintainable mapping layers in your Java applications.

