Beyond Lombok: Modern Java Code Generation Tools
As Java developer, you are likely familiar with Lombok, the popular Java library that helps reduce boilerpate code. The magic happens in…
As Java developer, you are likely familiar with Lombok, the popular Java library that helps reduce boilerpate code. The magic happens in compile time when Lombok generates setter, getters, constructors, etc. The Lombok despite its powerfull and popularity not only player in Java code generation space. In this article, we will explore two other modern Java generation tools: MapStruct and Java Poet. I will try to explain how it works under the hood
Lombok: Annotation — based code generation
Let’s briefly discuss how Lombok works. Lombok relies on annotations to generate code at compile time. The Lombok’s annotation processor kicks in during compilation and generates the corresponding Java bytecode.
The Lombok utilize Java’s annotation processing API to hook into compilation process. During compliation it scans your source files for annotations and modifies the abstract syntax tree (AST) of your code to include generated methods. This steps occurs before the javac converts the AST to bytecode, so from compiler’s perspective, it’s as if you wrote the boilerplate code yourself.
While Lombok is excellent tool for reduceing boilerplate code, it has some pitfalls. The Lombok’s primarily focused on generating code within a single class.
Here is an example of how Lombok annotations can be used:
@Getter
@Setter
@ToString
public class User {
private String name;
private int age;
@Builder
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
In code above we have a User
class with annotations @Getter, @Setter and @ToString.
These annotations instruct Lombok to generate the corresponding methods.
The @Builder
annotatition instructs Lombok to generate a builder patter for creating User objects.
User user = User.builder()
.name("John Doe")
.age(25)
.build();
MapStruct: Declarative Mapping between Java Beans
MapStruct helps developers with mapping between Java bean types. It is useful when you need to convert from one data transfer object to another or map data transfer object to entities.
MapStruct allows you define a mapper interface with methods that specify the source and target types for the mapping. As the Lombok code generation occurs in compile time.
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.get(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToDto(Car car);
}
In code above CarMapper interface declare method carToDto
that responsible for mapping from a car object to a CarDto
. The @Mapping
annotation specifies that property numberOfSeats
of Car
should be mapped to seatCount
property of CarDto
.
Example of code generated by MapStruct during compile time
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setSeatCount(car.getNumberOfSeats());
return carDto;
}
}
As a Lombok MapStruct uses processing API. Meanwhile, instead of modifying the abstract syntax tree of existing class, it generates entirely new source files containing implementation.
The MapStruct enable type safety. The validation of mapping happens at compile time, so in situation when you change structure of your classes you will ge compilation error if your mappers are no longer valid. There are a lot of useful features provided by MapStruct such as custom type converters, handling collections and maps, and defining mapping strategies for handling null values.
Java Poet: Programmatic Code Generation
JavaPoet is a Java library for generating Java source files. JavaPoet not rely on annotations which is differ from Lombok and MapStruct.
With JavaPoet, you build up the structure of a Java file using a fluent API. The fluent API enable you to define packages, imports, classes, fileds, methods and other different parts of source files. Below is a simple example that generates a “Hello, World!” class
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, World!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
After execution of this code we can copy output and compile and run code generated by JavaPoet
package com.example.helloworld;
import java.lang.String;
import java.lang.System;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
JavaPoet provides a flexible and powerful way to generate Java code. The case of application JavaPoet is when you need to generate code based on runtime data or when you are developing tools that generate code, such as annotation processors or code generators for other languages.
But there is challenge with JavaPoet. The challenge is that generated code can be hard to read and maintain than hand-written code. Comparing with annotation-based approaches like Lombok and MapStruct this method is more verbose.
Conslusion
Lombok, MapStruct, and JavaPoet are all powerfull tools in the Java developer’s toolbox for reducing boilerplate and generating code. Each tools has its own pros and cons.
- Lombok is way-to-go for reducing boilerplate code.
- MapStruct shine at generating type-safe mappers.
- JavaPoet provides a flexible programmatic API for generating Java source files.
As Java developer knowledge of how this tools work under the hood can help you make reasonable decision about when and how to use them in your projects.
References
- Project Lombok
- MapStruct — Java bean mapping, the easy way!
- JavaPoet
- Osvaldo, S., Forradellas, R., Villoria, R., & Oliva, H. (2022), Annotation-based code generation: A systematic mapping study. Journal of Computer Languages
- Luo, W., Chen, Z., Xu, X., Yan, J., & Zhang, H. (2019), An Approach of Automated Java Code Generation Based on Method-Level Aspects and Bytecode Injection. IEEE Access
- Nguyen, T. T., Nguyen, Q. M., Pham, H. V., & Nguyen, M. Q. (2019). Applying Model-Driven Development to Automate Code Generation for Web Applications. In 2019 11th International Conference on Knowledge and Systems Engineering (KSE) (pp. 1–8). IEEE.