There are a few topics that can turn programming community red hot. Plural or singular database table names? Should we use DTOs or not? And so on, and so forth. But that’s not the time to discuss these matters. Today we won’t deal with databases, but let’s face DTOs and map some of them around.

Some multilayer applications, or even some simple ones with a legacy code, need to take care of mapping some objects to another, usually domain objects to DTOs or different versions of DTOs. Say one is for administration, other for portal, and yet another for API. Or you need to map early version to the current one. We’ve all been there it’s not hard but rather bothersome and dull to write and maintain. Let’s look for some relief. It’s right around the corner I present you MapStruct.

MapStruct is a tool that can generate mapping classes between two types (aimed for DTOs). For a lot of cases all we need to do is create simple interface with @Mapper annotation. For the rest in need of some additional processing (different field names, combining different classes, flattening structure) we only have to explain differences within @Mappings annotation. MapStruct will run during e.g. Maven compilation phase (or wherever else you’d plug it in), look for those annotations and create or update appropriate mappings.

By now I probably lost attention of all the cool guys who don’t use DTOs. That’s fine, but they’ll be back some day. There are also some developers, who won’t let anyone or anything touch their mapping code, as they are the only ones to do that right. I bet they won’t waste time reading this and that’s good they need all the time possible to do manual mapping.

We don’t have all day for this either, so let’s get down to business, shall we? The use case will be simple I want to show you some basic capabilities of MapStruct.

We have the user and post objects for a blog system. It’s a new version, but there are some clients still using legacy API. That’s why we have the following two sets of DTOs:

The first thing to create is a simple mapper. In order to do that, we only need to prepare interface declaring proper mapping methods. This interface will be implemented by generated code.

Please note that since we’re dealing with collection mapping, we need to declare mapping for both collection and collection’s elements.

Here’s a sample test for our code:

Look what will happen when we run the test:

BlogNewsletterPropo_3 

Ok, we have one general mapper, which is fine for a simple project. But once we get more objects to map, we’d love to be able to split them. That’s of course possible. Just keep in mind that some mappers need to be aware of other mappers defining required methods. Here is an example of such a behaviour:

A user mapper needs to know where to look for methods mapping posts.

Right, now we are ready to do some serious stuff. As you could see, not many newUser fields were filled. If we inspect the compilation log, we can easily see the reason.

MapStruct simply doesn’t know how to map properties that differ in names. We should specify that:

We used 3 simple tricks at once (if you want me to explain them, there you go: translated field name for username, ignored lastPostAt and set isLegacy to true, since in our case it is always true when we are mapping objects).

The compilation warning is gone. Now let’s extend our test:

And run it:

BlogNewsletterPropo_5

 

Another useful feature is a possibility to add another parameter to mapping. We’ll use it to fill lastPostAt for user. We won’t ignore that field anymore but rather map it to another parameter like this:

Since now we have different parameters, we also need to specify a source for the login field, which is ‘value’ parameter.

Again, we need to update the test. First, we have to change the mapping call:

Now check the result:

Does it work? It seems so:

BlogNewsletterPropo_6

 

One more thing we can specify in @Mapping is expression returning desired value of a target field. We can simply put a Java code there.

Let’s assume we want to fill the post’s description with the beginning of post content. Our example post is quite short, so the description will cut only first 5 letters. The implementation is naive, but that’s not the point here.

 Even regardless of cutting implementation, it is nasty, isn’t it? In that case I’d rather go for creating some util and calling it in expression:

 

I’d say it’s slightly better. Now at least util implementation is testable. Unfortunately we need to use fully qualified names here.

But luckily that’s not the only way to do it. We can use abstract class to define mapping and provide full implementation of the method ourselves as follows:

In case you wonder − yes, that’s the method generated by MapStruct, only copied to abstract class and cleaned up a bit − util class was imported, and post variable was renamed from “post_”. This is a trivial example, but you can do all the magic you want this way. Let’s add one more test case and check if it works:

BlogNewsletterPropo_9

 

Yes, it did.

So there we are. We saw some basic features of MapStruct. There is a lot more, which you can check yourself on http://mapstruct.org. And if you miss some functionality, you can join the team at https://github.com/mapstruct/mapstruct

For now there is only one small problem: MapStruct is not officially released yet. But it’s getting there. The RC1 has been released recently, so MapStruct 1.0 should be ready any time soon. However we found it mature and stable enough to already introduce it in one of our projects, and it is doing quite well with much more than two users and posts. We find it both helpful and safe, since all the magic happens during the compilation phase.

I keep my fingers crossed for MapStruct development. Do you?

Software Developer always in search of better ways and tools but without cutting corners. On the life side of work-life balance ̶ aviation geek and car enthusiast, with rock’n’roll on the stereo.

  • Pavel Biely

    Hi Wojciech,
    Thank you for a great post!
    I have a similar case when one mapper uses another:

    @Mapper(uses = PostMapper.class)
    public interface UserMapper {

    And when unit testing UserMapper the other mapper (PostMapper) is not injected into it and is null that leads to test fail.
    Could you please share the source code of your project, maybe I missed something.
    Thank you!