avatarMilena Lazarevic

Summarize

Make Your Life Easier With MapStruct & Lombok

Speed up your development and get rid of boilerplate code

Lombok, Indonesia Photo by Tiket2 Fly on Unsplash

If you are working as a developer, you are already aware that speed is increasingly important. You have deadlines, sprints need to be finished, requirements change and you are under pressure to produce quality code as fast as possible. In that case, this article might not be for you, since you probably already know everything about MapStruct and Lombok. But, if you are still learning how to code, then this article will teach you the basics of these libraries and how to set them all up.

Introduction

Lombok (apart from being an island in Indonesia) and MapStruct are code generator libraries, that automatically plugs into your editor and build tools, spicing up your Java. Lombok generates getters, setters, constructors, builders etc.

MapStruct will use getters, setters, and builders generated by Lombok, to generate bean mappings at compile-time, ensuring high performance, which will save you from writing all that code.

This will greatly speed up your development and eliminate boilerplate sections of code that repeat in multiple places with little to no variation.

Dependencies and configuration

For our demo project, I’m using Gradle, but if you prefer Maven, you can find dependencies in the MapStruct and Project Lombok documentation. We need the following dependencies:

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-validation'
   implementation 'org.springframework.boot:spring-boot-starter-web'
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
   implementation 'org.mapstruct:mapstruct:1.4.2.Final'
   implementation 'org.projectlombok:lombok'
   runtimeOnly 'org.postgresql:postgresql'
   annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.1.0'
   annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

MapStruct code generator can be configured using annotation processor options to annotate generated mappers with Spring’s @Component stereotype. This will make generated mappers available for dependency injection as Spring Beans, but first, you need to enable this option by adding the following snippet to your build.gradle file so that this option will be passed to the compiler.

compileJava {
   options.compilerArgs += [
         '-Amapstruct.defaultComponentModel=spring'
   ]
}

Lombok Annotations

Let’s create our entities and annotate them with @Builder, @NoArgsConstructor, @AllArgsConstructor(access = AccessLevel.PRIVATE), @Getter and @Setter annotations. Builder is a creational design pattern that lets you construct complex objects step by step.

  • @Builder will generate builder
  • @AllArgsConstructor(access = AccessLevel.PRIVATE) will generate a private all-args constructor which requires one argument for every field in the class
  • @NoArgsConstructor will generate a constructor with no parameters
  • @Getter and @Setter will generate getters and setters for all fields in the class

After you build your project, you can find code generated by Lombok in your build folder:

Here is what the User class looks after Lombok added all the code for us:

That is 133 lines long and I don’t expect you to go through it all, but we only had to type 18 lines in our class, so you can see how Lombok saved us from coding all those getters, setters, constructors, and builder, which can become so tedious.

Let’s create UserDto next. Here we will use @Data annotation which bundles the features of @ToString, @EqualsAndHashCode, @Getter, @Setter and @RequiredArgsConstructor.

MapStruct Mappers

Now we define UserMapper by simply defining an interface annotated with @Mapper. This annotation tells MapStruct to create an implementation of the UserMapper interface and implement the mapping methods. Method toUserDto will map User to UserDto and toUser method will do the inverse mapping.

When two properties have the same name, in the source and target entity, for example Integer age property, it will be mapped implicitly. But, when properties have different names, for example String firstName in User and String name in UserDto, we have to specify source and target using @Mapping annotation.

UserMapperImpl can also be found in the build folder. Snippet bellow is the implementation of toUser method generated by MapStruct. It uses builder pattern generated by Lombok to create User object and getters to retrieve UserDto properties.

Since we configured MapStruct’s default component model to enable dependency injection in Spring, UserMapperImpl is automatically annotated with Spring’s @Component so we can now inject it in our service and use it to map UserDto to User object and save it in the database, then map saved user to UserDto and return it.

Another great feature of MapStruct mappers is that you can create your own custom methods, and then you can pass additional context information through generated mapping methods to custom methods with @Context parameters. Let’s say you have an AddressDto that has a String userId:

When mapping AddressDto to Address, you need to map that string to the user that is in the Address object. You also need to make sure that the specified user already exists in the database. So you need to use UserRepository to find that user first. You can pass UserRepository as context and the generated mapping method will use your custom method, which uses a passed repository to retrieve the User object.

Here is what MapStruct generated, it sets the user it retrieved using our custom method.

Notice as well, implicit type conversion between Enum/String and Integer/String.

MapStruct also supports mapping methods with several source parameters. For example in order to combine several entities into one data transfer object. The following shows an example mapping from User and Address objects to a UserInfoDto which contains combined properties.

MapStruct also supports mapping collections, so it’s simple to map List<Address> to List<AddressDto>. But it doesn’t support mappings from iterable to non-iterable type. So you can create custom method which calls generated mapping method to combine an object with a list.

Final words

MapStruct and Lombok have some very robust capabilities. They both have their pros and cons. Some people really like to use them, some don’t. They really enable you to get the job done with only a few lines of code. Which doesn’t mean you shouldn’t be careful. There is a lot of room for mistakes. Debugging can be quite hard sometimes, but don’t be afraid to try them out and see if it works for you and your project. MapStruct has a bit of a steep learning curve in my opinion, especially if you need to do some complex mappings. So make sure to go through the documentation and try out some examples.

You can find the example source code used in this article on GitHub.

Read next:

Programming
Spring Boot
Java
Lombok
Programming Languages
Recommended from ReadMedium