Entity To DTO Conversion for a Spring REST API

2. Model Mapper

Let"s start by introducing the main library that we"re going to use to perform this entity-DTO conversion – ModelMapper.

We will need this dependency in the pom.xml:

1
2
3
4
5
< dependency >
     < groupId >org.modelmapper</ groupId >
     < artifactId >modelmapper</ artifactId >
     < version >2.3.5</ version >
</ dependency >

To check if there"s any newer version of this library, go here.

We"ll then define the ModelMapper bean in our Spring configuration:

1
2
3
4
public ModelMapper modelMapper() {
     return new ModelMapper();
}

3. The DTO

Next, let"s introduce the DTO side of this two-sided problem – Post DTO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PostDto {
     private static final SimpleDateFormat dateFormat
       = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
 
     private Long id;
 
     private String title;
 
     private String url;
 
     private String date;
 
     private UserDto user;
 
     public Date getSubmissionDateConverted(String timezone) throws ParseException {
         dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
         return dateFormat.parse( this .date);
     }
 
     public void setSubmissionDate(Date date, String timezone) {
         dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
         this .date = dateFormat.format(date);
     }
 
     // standard getters and setters
}

Note that the two custom date related methods handle the date conversion back and forth between the client and the server:

  • getSubmissionDateConverted() method converts date String into a Date in server"s timezone to use it in the persisting Post entity
  • setSubmissionDate() method is to set DTO"s date to Post‘s Date in current user timezone.

4. The Service Layer

Let"s now look at a service level operation – which will obviously work with the Entity (not the DTO):

1
2
3
4
5
6
7
8
9
10
public List<Post> getPostsList(
   int page, int size, String sortDir, String sort) {
  
     PageRequest pageReq
      = PageRequest.of(page, size, Sort.Direction.fromString(sortDir), sort);
  
     Page<Post> posts = postRepository
       .findByUser(userService.getCurrentUser(), pageReq);
     return posts.getContent();
}

We"re going to have a look at the layer above service next – the controller layer. This is where the conversion will actually happen as well.

5. The Controller Layer

Let"s now have a look at a standard controller implementation, exposing the simple REST API for the Post resource.

We"re going to show here a few simple CRUD operations: create, update, get one and get all. And given the operations are pretty straightforward, we are especially interested in the Entity-DTO conversion aspects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class PostRestController {
 
     @Autowired
     private IPostService postService;
 
     @Autowired
     private IUserService userService;
 
     @Autowired
     private ModelMapper modelMapper;
 
     @GetMapping
     @ResponseBody
     public List<PostDto> getPosts(...) {
         //...
         List<Post> posts = postService.getPostsList(page, size, sortDir, sort);
         return posts.stream()
           .map( this ::convertToDto)
           .collect(Collectors.toList());
     }
 
     @PostMapping
     @ResponseStatus (HttpStatus.CREATED)
     @ResponseBody
     public PostDto createPost( @RequestBody PostDto postDto) {
         Post post = convertToEntity(postDto);
         Post postCreated = postService.createPost(post));
         return convertToDto(postCreated);
     }
 
     @GetMapping (value = "/{id}" )
     @ResponseBody
     public PostDto getPost( @PathVariable ( "id" ) Long id) {
         return convertToDto(postService.getPostById(id));
     }
 
     @PutMapping (value = "/{id}" )
     @ResponseStatus (HttpStatus.OK)
     public void updatePost( @RequestBody PostDto postDto) {
         Post post = convertToEntity(postDto);
         postService.updatePost(post);
     }
}

And here is our conversion from Post entity to PostDto:

1
2
3
4
5
6
private PostDto convertToDto(Post post) {
     PostDto postDto = modelMapper.map(post, PostDto. class );
     postDto.setSubmissionDate(post.getSubmissionDate(),
         userService.getCurrentUser().getPreference().getTimezone());
     return postDto;
}

And here is the conversion from DTO to an entity:

1
2
3
4
5
6
7
8
9
10
11
12
private Post convertToEntity(PostDto postDto) throws ParseException {
     Post post = modelMapper.map(postDto, Post. class );
     post.setSubmissionDate(postDto.getSubmissionDateConverted(
       userService.getCurrentUser().getPreference().getTimezone()));
  
     if (postDto.getId() != null ) {
         Post oldPost = postService.getPostById(postDto.getId());
         post.setRedditID(oldPost.getRedditID());
         post.setSent(oldPost.isSent());
     }
     return post;
}

So, as you can see, with the help of the model mapper, the conversion logic is quick and simple – we"re using the map API of the mapper and getting the data converted without writing a single line of conversion logic.

6. Unit Testing

Finally, let"s do a very simple test to make sure the conversions between the entity and the DTO work well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class PostDtoUnitTest {
 
     private ModelMapper modelMapper = new ModelMapper();
 
     @Test
     public void whenConvertPostEntityToPostDto_thenCorrect() {
         Post post = new Post();
         post.setId(1L);
         post.setTitle(randomAlphabetic( 6 ));
         post.setUrl( "www.test.com" );
 
         PostDto postDto = modelMapper.map(post, PostDto. class );
         assertEquals(post.getId(), postDto.getId());
         assertEquals(post.getTitle(), postDto.getTitle());
         assertEquals(post.getUrl(), postDto.getUrl());
     }
 
     @Test
     public void whenConvertPostDtoToPostEntity_thenCorrect() {
         PostDto postDto = new PostDto();
         postDto.setId(1L);
         postDto.setTitle(randomAlphabetic( 6 ));
         postDto.setUrl( "www.test.com" );
 
         Post post = modelMapper.map(postDto, Post. class );
         assertEquals(postDto.getId(), post.getId());
         assertEquals(postDto.getTitle(), post.getTitle());
         assertEquals(postDto.getUrl(), post.getUrl());
     }
}

7. Conclusion

This was an article on simplifying the conversion from Entity to DTO and from DTO to Entity in a Spring REST API, by using the model mapper library instead of writing these conversions by hand.

The full source code for the examples is available in the GitHub project.