If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

The new Certification Class of REST With Spring is out:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll handle the conversions that need to happen between the internal entities of a Spring application and the external DTOs (Data Transfer Objects) that are published back to the client.

Further reading:

A Guide to Mapping With Dozer

Dozer is a Java Bean to Java Bean mapper that copies data from one object to another, attribute by attribute, supports mapping between attribute names, does type conversion, and many other things.

Read more

Quick Guide to MapStruct

A quick and practical guide to using MapStruct

Read more

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:

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>0.7.4</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:

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

3. The DTO

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

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 2 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 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):

public List<Post> getPostsList(
  int page, int size, String sortDir, String sort) {
 
    PageRequest pageReq
     = new PageRequest(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:

@Controller
class PostRestController {

    @Autowired
    private IPostService postService;

    @Autowired
    private IUserService userService;

    @Autowired
    private ModelMapper modelMapper;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List<PostDto> getPosts(...) {
        //...
        List<Post> posts = postService.getPostsList(page, size, sortDir, sort);
        return posts.stream()
          .map(post -> convertToDto(post))
          .collect(Collectors.toList());
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public PostDto createPost(@RequestBody PostDto postDto) {
        Post post = convertToEntity(postDto);
        Post postCreated = postService.createPost(post));
        return convertToDto(postCreated);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public PostDto getPost(@PathVariable("id") Long id) {
        return convertToDto(postService.getPostById(id));
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @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:

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:

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:

public class PostDtoUnitTest {

    private ModelMapper modelMapper = new ModelMapper();

    @Test
    public void whenConvertPostEntityToPostDto_thenCorrect() {
        Post post = new Post();
        post.setId(Long.valueOf(1));
        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(Long.valueOf(1));
        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.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE CLASSES

Sort by:   newest | oldest | most voted
Siva
Guest

Hi,
Thanks for the article.
I just want to know how it works if it has child collection and bi-directional mapping such as Post has List comments and Comment has Post reference?

Eugen Paraschiv
Guest

Hey Siva – that’s definitely an interesting question. For now this is done manually, but exploring alternatives with the ModelMapper is now on the TODO list, so if it’s supported, I’ll add a new section to this article.
Cheers,
Eugen.

Puneet Pandey
Guest

I think @JsonManagedReference and @JsonBackReference would manage bi-directional child collection mapping.

@JsonManagedReference
private List comments;

@JsonBackReference
private Messafes messages;

Richard
Guest

SimpleDateFormat (and all java.text.Format subclasses) is not thread safe, so you cannot safely use a static

Eugen Paraschiv
Guest

Hey Richard – nice catch, looking into it. Cheers,
Eugen.

Yasitha
Guest

Just want to know your opinion about “why conversion happens in controller layer? ”
What is the good practice?
1. Convert in the service layer and return DTOs to controller, so controller just return DTOs
2. As you implemented, Service returns model object and controller does the conversion.

Which one is the best approach?

Eugen Paraschiv
Guest
Hey Yasitha – that will of course depend on what your architecture looks like. Typically, that’s where these responsibilities exist – the service layer doesn’t know about DTOs, so the Controller layer handles the conversion before calling the service – that way you can cleanly separate the responsibilities. However, there’s no idiomatic way to go here – it’s simply a matter of what fits best with your architecture. For example, if you controller already has a number of other responsibilities, you may want to push this one down to the service. If it doesn’t, then this one is a good,… Read more »
Given Nyauyanga
Guest

Well for me I like my controllers clean and tidy, so I made sure that all my services return a DTO and never an entity. So after doing what needs to be done in a service method, I would convert the entity object and return a DTO 🙂 It really looks clean. Also I created a conversion service bean that has many methods for different DTO conversions. If I needed to use the bean, I would just Autowire it in the service and use the methods I need. This also made my services clean.

Saurabh
Guest

How should we implement the bidirectional mapping in case we are using custom propertyMap

Eugen Paraschiv
Guest

I’ll have to look into it to see exactly what can be done there.
Feel free to open an issue over on github (and include test that exemplifies the problem) – and I’ll have a look.
Cheers,
Eugen.

Kanagha
Guest

Is using a DTO similar to the concept where we do not want to expose model to the end user and just exposing a view version of it instead?
In that case, it must not have any setters. Just a constructor with the entity object as input param.

Eugen Paraschiv
Guest

In a way. A DTO is indeed all about separation of concerns and is the next logic step if you’re exposing your actual entities (which is not a good idea for quite a few reasons).
What I’m not clear on is the part with “no setters and getters” – can you elaborate on that aspect?
Cheers,
Eugen.

Kanagha
Guest

Thanks for the clarification! What I meant is was, a DTO doesn’t require any setter methods. It can just have a constructor that takes the entity object as input.

Eugen Paraschiv
Guest

Sure, that’s perfectly fine. Just be aware of the fact that some frameworks (such as Jackson) will use setters and getters, so you may have to still have them defined on your DTO even if you’re not using them.

wpDiscuz