Mastering ModelMapper in Spring Boot

Rabinarayan Patra
5 min readFeb 29, 2024

Introduction to ModelMapper

ModelMapper is a powerful Java library designed to simplify the process of mapping objects from one type to another. It automates the tedious task of manually transferring data between objects, which is especially useful in layered architectures, such as MVC applications, where you often need to transfer data between model, DTO (Data Transfer Objects), and view layers.

Why ModelMapper?

In software development, especially in complex applications, the task of mapping fields from one class to another is common. Doing this manually not only leads to boilerplate code but also increases the risk of errors. ModelMapper addresses these issues by:

  • Reducing boilerplate code.
  • Increasing code readability and maintainability.
  • Providing a simple, convention-based mechanism to transform objects.

How ModelMapper Works

ModelMapper works on the principle of convention over configuration. It uses a set of conventions to intelligently map properties between objects. If properties in the source and destination objects follow JavaBean naming conventions and are compatible, ModelMapper will automatically map them without additional configuration.

Internals

At its core, ModelMapper constructs a mapping graph between source and destination types, identifying matching properties by name and type. When a mapping is executed, ModelMapper traverses this graph, copying values from the source to the destination.

Setting Up ModelMapper in Spring Boot

ModelMapper can be easily integrated into a Spring Boot application. Here’s how to add it to your project using Maven and Gradle.

Maven Setup

Add the following dependency to your pom.xml:

<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.8</version>
</dependency>

Gradle Setup

For Gradle, include this in your build.gradle:

implementation 'org.modelmapper:modelmapper:2.3.8'

Ensure you’re using the latest version by checking the ModelMapper website.

Spring Boot Configuration

To use ModelMapper in Spring Boot, you can define a @Bean for ModelMapper in your configuration:

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {

@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}

This makes ModelMapper available for autowiring throughout your Spring Boot application.

Usage and Examples

ModelMapper shines when converting between different object types. Here’s a basic example of mapping between an Entity and a DTO:

public class UserEntity {
private String name;
private String email;
// Getters and setters omitted for brevity
}

public class UserDTO {
private String name;
private String email;
// Getters and setters omitted for brevity
}

// Autowire ModelMapper
@Autowired
private ModelMapper modelMapper;

public UserDTO convertEntityToDTO(UserEntity userEntity) {
return modelMapper.map(userEntity, UserDTO.class);
}

Advanced Configuration

ModelMapper offers various configuration options to customize mappings. These include:

  • Property Mapping: Customize how individual properties are mapped.
  • Conditionals: Add conditions to control whether fields are mapped.
  • Converters: Use custom converters for complex mappings.

Example: Custom Property Mapping

modelMapper.typeMap(UserEntity.class, UserDTO.class).addMappings(mapper -> {
mapper.map(src -> src.getName(), UserDTO::setName);
});

Advanced Configuration in Depth

ModelMapper’s power lies in its ability to be finely tuned through advanced configurations. Beyond basic object mapping, it offers several features that cater to complex mapping scenarios, such as conditional mapping, custom converters, and property mapping.

Property Mapping

Property mapping allows you to explicitly define how properties between two objects are mapped. This is especially useful when property names or structures don’t match.

modelMapper.typeMap(SourceClass.class, DestinationClass.class).addMappings(mapper -> {
mapper.map(SourceClass::getSourceName, DestinationClass::setDestinationName);
});

Conditional Mapping

Conditional mapping lets you control whether a field should be mapped based on specific conditions.

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
mapper.when(Conditions.isNotNull()).map(Source::getConditionalValue, Destination::setValue);
});

Using Converters for Complex Mappings

Converters are powerful tools within ModelMapper that allow for custom logic during the mapping process. This is particularly useful for complex transformations.

modelMapper.typeMap(Source.class, Destination.class).setConverter(context -> {
Source source = context.getSource();
Destination dest = new Destination();
// Custom logic to populate dest from source
return dest;
});

Patch Mapping for Partial Updates

Patch mapping is a technique used in APIs for partial updates of resources. Instead of updating an entire resource, only specified fields are updated. ModelMapper can be tailored to support patch mapping elegantly.

public void patchEntity(Destination source, Destination destination) {
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
modelMapper.map(source, destination);
}

Integration with Spring Boot REST APIs

In a Spring Boot application, ModelMapper can be utilized to facilitate the transformation of data between entities and DTOs within REST controllers. Here’s an example of how ModelMapper can be used in a Spring Boot REST controller:

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private ModelMapper modelMapper;

@Autowired
private UserService userService;

@PatchMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserDTO userDto) {
UserEntity userEntity = userService.findById(id);
modelMapper.map(userDto, userEntity);
userService.save(userEntity);
UserDTO updatedUserDto = modelMapper.map(userEntity, UserDTO.class);
return ResponseEntity.ok(updatedUserDto);
}
}

Understanding ModelMapper’s Internal Workings

Internally, ModelMapper employs a robust mapping engine that analyzes object structures through introspection. It then builds a mapping graph that connects source and destination properties. When a mapping is invoked, ModelMapper traverses this graph, applying any configured converters, conditions, and property mappings. This approach ensures efficient and accurate mappings, even in complex scenarios.

Additional Features and Methods in ModelMapper

Deep Mapping

ModelMapper excels at deep mapping, where nested objects need to be transformed from one structure to another. It automatically handles nested properties without requiring explicit instructions, provided the property names and types in the source and destination objects match.

Custom Property Mapping

For scenarios where property names do not match or custom logic is needed, ModelMapper allows for specific property mappings:

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
mapper.map(src -> src.getNested().getDeepProperty(), Destination::setDifferentName);
});

This is particularly useful when working with complex object graphs or when integrating systems where data structures differ significantly.

Conditional Skip

There might be cases where you want to skip mapping certain fields under specific conditions. ModelMapper provides a way to conditionally skip fields:

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
mapper.skip(Destination::setFieldToSkip);
});

Value Converters

ModelMapper allows for the specification of custom value converters if you need to perform specific transformations:

Converter<String, String> toUpperCaseConverter = context -> context.getSource() == null ? null : context.getSource().toUpperCase();

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper ->
mapper.using(toUpperCaseConverter).map(Source::getName, Destination::setName));

Pre/Post Converters

Pre and post converters offer hooks that execute before and after the mapping process, allowing for additional customization or validation:

modelMapper.typeMap(Source.class, Destination.class).setPreConverter(context -> {
// Pre-conversion logic here
return context;
});

modelMapper.typeMap(Source.class, Destination.class).setPostConverter(context -> {
// Post-conversion logic here
return context;
});

Implicit Mapping

ModelMapper performs implicit mapping when possible, relying on the structure of the source and destination types to automatically determine how data should be transferred. This reduces the need for explicit configuration while still allowing for customization as needed.

Mapping Inheritance

ModelMapper supports mapping inheritance, allowing for a base configuration to be extended by more specific mappings. This is particularly useful for DRY (Don’t Repeat Yourself) principles in complex mapping scenarios where base object mappings are common but need slight adjustments for specific cases.

modelMapper.createTypeMap(BaseSource.class, BaseDestination.class)
.addMapping(BaseSource::getBaseProperty, BaseDestination::setBaseProperty);

modelMapper.createTypeMap(SpecificSource.class, SpecificDestination.class)
.includeBase(BaseSource.class, BaseDestination.class);

Conclusion

Exploring these additional features and methods reveals the depth and flexibility of ModelMapper. Whether dealing with simple or complex mapping requirements, ModelMapper provides the tools necessary for efficient and effective data transformation. Leveraging these capabilities can lead to cleaner code, reduced boilerplate, and a more maintainable codebase in Java and Spring Boot applications.

ModelMapper’s extensive documentation and JavaDoc serve as excellent resources for developers seeking to maximize their use of the library, ensuring that they can tackle a wide array of data mapping challenges with confidence.

This blog post has aimed to provide a comprehensive overview of ModelMapper, illustrating its importance, functionality, and integration into Spring Boot applications. Through practical examples and explanations of advanced configurations, developers can harness ModelMapper to enhance code quality and efficiency in their projects.

--

--

Rabinarayan Patra

I'm a Software Engineer with 2+ years of experience, passionate about tech and teaching programming. Business Email - rabi-work@outlook.com. YT- 'Code Converse'