Migrating From Lombok to Kotlin
Shorter code should never be the goal. It’s the readability of the code that matters.
Join the DZone community and get the full member experience.
Join For FreeAs a Java developer, one of the most commom complaints about working in Java is the verbosity of the language. One of the main areas where this verbosity really shows up is in the area of data classes. Data classes, or tuples or records, one day might end up in the Java language, but until that day, whenever one creates a REST DTO, JPA Entity, Domain class, or anything like this, Java's verbosity shows up. In this article, I'll describe how to migrate from Lombok to Kotlin and some of benefits that come from this migration.
// 40 Lines of Java code for a class with 2 properties
import java.time.LocalDate;
import java.util.Objects;
public class Person {
private String name;
private LocalDate dateOfBirth;
public Person(String name, LocalDate dateOfBirth) {
this.name = name;
this.dateOfBirth = dateOfBirth;
}
public String getName() {
return name;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(dateOfBirth, person.dateOfBirth);
}
@Override
public int hashCode() {
return Objects.hash(name, dateOfBirth);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
To effectively use Data classes, you often end up with a series of properties; a constructor; a series of getters; perhaps, an equals; hashcode and a toString
method; and, in some alternative, corporate world, an evil setter here or there. Since this is such a common issue, several solutions have been created — Lombok being the most well known, but alternatives such as AutoValue and Immutables also exist.
However, in this blog post, I’m going to focus on moving away from Lombok to Kotlin, since it’s a great way to get started with Kotlin, it’s a low risk and easy to understand, plus Kotlin provides many benefits over Java, thus migrating to Kotlin Data classes is a great start to start adopting Kotlin in the rest of your codebase.
Small disclaimer: while this blog post focuses on the migration to Kotlin, I in no way think that Lombok is bad. It’s a great tool and provides a lot of benefits for standard Java code. This is merely a demonstration on how to use Kotlin in places where Lombok is currently used.
What Is Lombok?
For those unfamiliar with Lombok, Lombok is a generator library that removes the verbosity of Java code. For example, in the above class, using the Lombok library, the code would look like this:
import java.time.LocalDate;
import lombok.Value;
@Value
public class Person {
private String name;
private LocalDate dateOfBirth;
}
This is much nicer, right? The @Value
annotation, in this case, creates the class final, a constructor with the two parameters, getters, equals, hashcode, and toString
.
What Is Kotlin?
While the above is a huge improvement over the original code, this post is focusing on the migration to Kotlin. As such, our initial example can be rewritten in Kotlin with the following code:
data class Person(val name: String, val dateOfBirth: LocalDate)
This code does the same as the Lombok code, creating the constructor, a toString
, equals/hashcode, etc.
While this is even shorter, shorter code should never be the goal. It’s the readability of the code that matters. In this case, one could argue that both are equally readable, and I’d tend to agree with that. However, by introducing the Kotlin version, the equal readability of both is a great reason to move to Kotlin. The above code is 100 percent interoperable with the rest of the Java codebase. It, therefore, shouldn’t be a hard sell to introduce Kotlin into the code.
Lombok to Kotlin Migration Guide
While the above was just a small example, the following table will show a complete overview of how to move to Kotlin data classes.
Feature | Lombok | Kotlin | Note |
Final local references | val | val | val is a Kotlin keyword |
Reassignable local references | var | var | var is a Kotlin keyword |
Non-nullable references | @NonNull | No keyword needed | In Kotlin, types are nonnullable by default, and need to be explicitly declared nullable with a question mark, ie String?. |
Automatic Resource Management (ARM) | @Cleanup | Closeable.use | For example: val result = FileInputStream("input.txt").use { input -> //Process input } |
Create getters and setters | @Getter/@Setter | Part of data classes by declaring properties as var in data classes | For example: data class Person(var name: String) Creates a getter and setter of name on Person. |
Create a toString | @ToString | Part of data classes | For example: data class Person(var name: String) Creates an automatic toString on Person. |
Create a equals and hashcode method | @EqualsAndHashCode | Part of data classes | For example: data class Person(val name: String) Creates an automatic equals and toString on Person. |
Create a constructor without any arguments | @NoArgsConstructor | Part of data classes by giving all arguments a default value or by introducing a secondary constructor | For example: data class Person(val name: String = “”) Assigns a default value to name and creates a default no-arg constructor. Alternatively, create a secondary constructor: data class Person(var name: String) { constructor() : this(“”) } |
Create a constructor with parameters equal to the number of properties defined | @RequiredArgsConstructor and @AllArgsConstructor |
Part of data classes | For example: data class Person(val name: String) Automatically creates a constructor for all parameters. |
Create a mutable data class | @Data | Part of data classes by using ‘var’ in field declarations | For example: data class Person(var name: String) Automatically creates the toString, hashCode, equals, etc. |
Create an immutable data class | @Value | Part of data classes by using ‘val’ in field declarations | For example: data class Person(var name: String) Creates an automatic toString on Person. |
Object creation using named properties | @Builder | Named arguments in Kotlin | Person(name = “Sergey”, age = 25) |
Convert checked exceptions into unchecked exceptions | @SneakyThrows | All checked exceptions called from Kotlin code are unchecked. | Kotlin methods declared with @Throws, when called from Java, still can throw a checked exception. |
Synchronised methods using locks. | @Synchronized | Kotlin withLock method. It’s not completely the same, but it’s close. A better alternative is to look at Kotlin coroutines | someLock.withLock { sharedResource.operation()} |
Lazy property initialization. | @Getter(lazy=true) | Delegated properties | `by lazy` |
Automatic logger | @Log | No built-in alternative... | ...but a marker interface would make this easy to implement. |
As you can see in the table above, most features of Lombok are available in Kolin. However, what is so great about Lombok is it's flexibility. For example, it’s easy to add a toString
method to a class without adding an Equals
/HashCode
method. In Kotlin, this is not easily done.
In practice, the need for just a toString
method is, in my experience, not something that happens often, but it’s good to know that Lombok is a bit more flexible in this regard than Kotlin.
How to Add Kotlin Support to a Project
To start the migration, you’ll need to add Kotlin support to your project. You can easily do this by adding Kotlin support to your Maven project, or by adding Kotlin support to your Gradle project.
Using Kotlin and Lombok at the same time is not a great idea, since the compilation of Kotlin source code happens in the same phase as the Lombok code generation. As a result, the Kotlin code cannot use the Lombok-generated methods. You can work around this by putting the code into a separate project, but my recommendation would be to either migrate completely in one go, or, something that we did, you can deLombok the project and slowly migrate to Kotlin. Whichever approach you take depends mostly on your project size, but for us, the easiest was to deLombok the project and convert it to Kotlin.
Conclusion
I hope the above guide introduced Kotlin into your project. It should be a safe and readable conversion, which provides a basis for introducing more advanced Kotlin features into the project at a later stage, such as more idiomatic Kotlin code, coroutines, Kotlin typesafe DSLs, and much more.
Happy coding!
Published at DZone with permission of Erik Pragt. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments