“If you want to not write or even manually auto-generate the Getters and Setters in POJOs (Plain Old Java Objects) and want to get two POJOs mapped with less code, then read on for step by step instructions. Skip to Figure 1 for visual summary of this”.
Java has faced the criticism of being verbose in its syntax from the fans of other languages, and it’s quite fair, to be honest. With time, Java has evolved particularly to reduce its syntax verbosity with milestones from advanced for-each loop in Java 5 to try-with-resources in Java 7 to lambda expressions introduced in Java 8. All of these improvements address this issue elegantly, and make Java coding a lot easier and concise. Combine this with Spring Boot and you have a coding environment that’s miles ahead of Java coding environment from just a few years back in terms of speed of coding!
Still, there are things that a Java programmer has to do over and over again just to get the code complete from language/syntax point-of-view rather than spending time on business logic, the actual task at hand. Having to write or even auto-generate Getters and Setters in POJOs is one such issue. Having to map POJOs for data transfer from one layer to another is another cumbersome task that mostly just feels like a mere overhead. For example, presentation layer sees an entity differently than the service layer which in turn has a different shape from the same entity in persistence/DB layer. A lot of times developers find themselves writing mapping of these POJOs. What is even more painful is that maybe only a few fields differ and most of the rest of the fields have same name in source and target objects but we end up having to get and set them all from one object to another.
The above two problems can be addressed quite elegantly using a behind-the-scenes code generator like Project Lombok (introduced to me by my colleague, Carter) and a POJO mapper like MapStruct. This post will go through the example of eliminating the need to write or auto-generate Getters and Setters and of more concise mapping of one POJO to another (rather than manually doing something like target.setField(source.getField())).
Fig 1 displays the summary of this post and compares traditional POJOs and mappings with tools-facilitated light weight code to achieve same functionality.
Lombok provides annotations that can be declared on POJOs instead of writing Getters and Setters, Constructors, Loggers, etc. MapStruct is providing an automatic way of mapping POJOs, especially the ones that have attributes with same name and type, also using annotations. This can reduce a lot of development time and developers can focus on implementing business logic.
Code Walk Through
Following is a step-by-step walk-through to achieve the results in Fig 1. The environment is Eclipse (Spring Tool Suite IDE), Gradle (for building project and running jUnit test-cases), and Java 8 (on Mac OS X, although this shouldn’t matter other than menu buttons walk-through etc.).
(The code in this post will be shared here through GitHub soon. You can then download it from there instead of going through all the steps and use these steps as reference to read through where ever you need more details.)
- Let’s start by creating a new project in Eclipse:
2. Final Settings:
3. The project should like this with a default build.gradle file:
4. Next, we’ll change the default package to our own and start adding POJOs to it as follows:
We are assuming a scenario where we have a DTO (Data Transfer Object) POJO on one hand and an Entity (can be JPA or Hibernate or some other ORM or even manual SQL entry POJO), and we need to convert and copy data from one object to the other, bidirectional.
5. Let’s add FoodSampleDTO and use Eclipse to generate Getters and Setters (we will eventually eliminate this step):
6. And Select All fields and Finish:
This is what FoodSampleDTO looks like:
7. Follow same steps to create ProductDTO, which is a sub-object of FoodSampleDTO:
8. Let’s do the same for the POJO for our (superficial) persistence layer, SampleFoodEntity, to get:
You’ll notice that most of the fields are same in these POJOs with the exception of productId, which is a direct String field in FoodSampleEntity while it’s a sub-object in FoodSampleDTO. This is just an example and in real-world the POJOs will be much larger and differences can be much greater.
So now we want some mapper class to map data between DTO and Entity POJOs. This mapper will basically get data from one object and set it into another, with any necessary transformations.
9. Add FoodSampleMapper class in …mapper package and add two methods, one to convert DTO to Entity and other to convert Entity to DTO:
This is the second place where we want to reduce the manual work. As you can see, even though most fields have same name and type, we still have to get from one object and set into the other one. This can be cumbersome (and boring!) when the POJOs are large and in large number, which is typically the case.
Before looking at how to reduce these, let’s first write and run jUnit test-cases.
10. Under the source folder ‘src/test/java’, add a package …mapper and add a class FoodSampleMapperTests. This will have two methods to test our mapper’s two methods:
As you can see, we create our source object, populate some data in it, call our mapper to convert it to the other object and assert to ensure the data got copied and matches our original test data variables.
11. Run jUnit test-cases:
This is all good and business as usual. But cumbersome and boring to write and manually auto-generate this code.
Let’s reduce some coding!
First thing we are going to reduce is Getters and Setters using Lombok. Lombok provides annotations at compile-time to identify that you need Getters and Setters and hooks into the build process to generate the code. The POJOs stay lean (for check-in, check-out and editing purposes) but get the functionality of Getters and Setters auto-generated for run-time. Let’s setup Lombok.
12. Go to https://projectlombok.org/download and get the latest version of Lombok jar. Instructions for different IDEs and Build Tools can be found under “Install” drop down menu on the top bar at Lombok’s site.
13. Execute this jar to get the installer screen:
14. Select your IDE, hit Install/Update, then Quit Installer and restart your IDE:
15. Following the instructions on Lombok’s page, we’ll add a plugin and dependencies in our build.gradle file (the top section and last two lines, remember to save the file):
16. In Eclipse, Right-click the project, select Gradle > Refresh Gradle Project. This should resolve all new dependencies added to build.gradle file and bring them to Eclipse’s dependencies list. (This is where you might need to have a Gradle plugin for Eclipse, I’ve this one: http://marketplace.eclipse.org/content/buildship-gradle-integrationinstalled through Help > Eclipse Marketplace). Alternatively, you can use command line to build Gradle projects (you don’t even need to install Gradle just use gradlew files created in the project for commands like “./gradlew clean build”).
If all goes well, you should have Lombok provided annotations available in your project.
17. Remove all Getter and Setter methods and constructors from your POJOs and just annotate the POJO with Getter and Setter annotations:
Repeat this for all POJOs. Our POJOs now look like this:
Quite slick, eh? We’ve eliminated the need to auto-generate Getters and Setters and even writing constructors. We added a @NoArgsConstructor to FoodSampleEntity as it will be needed for MapStruct to work later on. We also added a @RequiredArgsConstructor to ProductDTO and made id a required field by using @NonNull, this way we can get the one-arg constructor auto-generated for us that’s needed by our existing test-cases: ProductDTO testProduct = new ProductDTO(testProductId);
Now we need to Clean/Build the project and re-run the jUnit test-cases to ensure they pass after these changes. We’ll setup Eclipse to invoke Gradle build from inside Eclipse instead of going to terminal for command line excecution.
18. Right click the build.gradle file, select Run As > Run Configurations, Choose Gradle Project in the left pane, click ‘New’ icon on top left of this pane and configure the Gradle build as follows and click Run:
This should result in a successful Gradle build, if all goes well:
The Console View should show something like:
The test task is successfully executed, passing our test-cases. Good!
Now, it’s time to get rid of that boring getFromSomeObject and setToAnotherObject mappings and replace it with simple annotations (only if field name and type is different! otherwise no mapping needs to be written as is the case for four of our five fields in POJOs).
So let’s setup MapStruct!
19. All we have to do is add MapStruct dependencies and Refresh Gradle Project:
20. Let’s create an interface ‘FoodSampleAutoMapper’ next to our (manual) mapper ‘FoodSampleMapper’, and annotate it with @Mapper (should be available if above dependencies have been successfully resolved):
We added two (abstract) methods to this interface, one for conversion of DTO to Entity and other for Entity to DTO. This small interface definition is all that’s needed to copy all fields with same name and type from one POJO to another! Incredible!
We’ll get to the fields that don’t have same name and type (like productId in our case). We also added an INSTANCE variable that will refer to an actual implementation class that implements this interface. But we don’t have to write that class, MapStruct will auto-generate this impl class for us behind the scenes and make it available at run-time much like Lombok does its magic. We don’t have to use INSTANCE variable, we can get the impl class inline with how our project searches for classes (e.g. you can use dependency injection using Spring, the @Mapper will need to be changed to @Mapper(componentModel = “spring”) and this way the generated class will have @Component annotation for Spring’s initialization to detect and make it available for DI).
21. We can simply switch our mapper to this interface. We’ll add another test class instead of modifying existing one, just copy/paste FoodSampleMapperTest class to FoodSampleAutoMapperTest class and change one line of code, to fetch the new mapper:
//Convert the DTO to entity object
FoodSampleEntity foodSampleEntity = FoodSampleAutoMapper.INSTANCE.mapFoodSampleDTOToEntity(foodSampleDTO);
We are using our interface’s INSTANCE method which will have the implementation class searched by Mappers.getMappers… call and this mapper class will be made available for our use. We can invoke mapFoodSampleDTOToEntity and mapFoodSampleEntityToDTO methods on the INSTANCE variable.
There’s no change in our test-cases except for above line:
22. Let’s try to run unit-tests which should fail. (Just go to Gradle Executions View (use Window > View > Other > (Type) Gradle if it doesn’t show up) and just press re-run icon to execute the build again. Alternatively, right click the project, Run As > Run Configurations and select Gradle Project config created in step # previously and Click Run.
As you can see, our previous test-cases are passing but new ones failed. The console on the right is showing two warnings that there’s an unmapped target property ‘productId’ in first mapper method and ‘product’ in the other one. This is because it is the only field in our POJOs that does not have same name and type in both POJOs so mapstruct doesn’t know how to convert this and it ignores it and just prints a warning. If we didn’t need this we can ignore the warning but in our test-cases we are expecting productId to be converted as well, therefore the actual failure of our test-cases throwing NullPointerException and AssertionError in line 71 and 40 respectively of our FoodSampleAutoMapperTest class. This is expected since we didn’t tell MapStruct how to map this field.
23. Now, let’s tell MapStruct how to map fields that are not that straight forward. Let’s use the powerful @Mapping annotation with attributes source and target to tell MapStruct how to convert productId to product.id:
We are telling MapStruct that for mapFoodSampleDTOToEntity method, when you are looking for productId (which is String in FoodSampleEntity target object), simply get the value from (the source) foodSampleDTO’s product field’s id field. And vice versa in mapFoodSampleEntityToDTO. Please note that we can add as many @Mapping lines as we need to specify the differences for each method, here we only have one difference per method, therefore only one @Mapping line.
In our example, we are just showing a difference in structure but in typical scenarios you will also find difference in types etc which will need more than just source/target mapping (see MapStruct’s excellent documentation for those cases, e.g. you can use expression and provide a Java line of code which will be placed as is in the generated class, providing a concise but powerful touch to these auto-mappings). You can also use dateFormat and MapStruct will automatically write DateParsing code with handling of DateParsing Exceptions. It can also do null checks and that strategy is also customizable, again refer to MapStruct documentation.
24. Run test-cases again with above change and these should pass with flying colors:
So there you have it, we have successfully reduced the code from Getters and Setters and manual mapping to some mere annotations:
Please let me know in comments if you find this post helpful or not, have any questions, issues or feedback.