Migrating from Lombok to Kotlin

As a Java developer, one of the most heard complaints about working 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, might one day end up in the Java language, but until that day, whenever one creates a Rest DTO, a JPA Entity, a Domain class, or anything like this, one of the areas where Java is the most verbose shows up.

// 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, maybe an equals, hashcode and toString method, and, in some alternative corporate world, even an evil setter here or there. Since this is such a common issue, several solutions have been created, Lombok being to the most well known, but alternatives such as AutoValue and Immutables exist.

However, in this blogpost, 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 blogpost focuses on the migration to Kotlin, I in no way think Lombok is bad. It’s a great tool and provides a lot of benefits over 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, 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;
}

So much nicer, right? The @Value annotation in this case makes the class final, creates a constructor with the 2 parameters, creates the getters, and an equals, hashcode and toString.

What is Kotlin?

While the above is a huge improvement over the original code, this post is focussing on a 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 also the constructor, a toString, equals/hashcode, etc.

While this is even shorter, shorter code should never be a goal. It’s the readability of the code which matters. In this case, one could argue that both 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% interoperable with the rest of the Java codebase. It therefore shouldn’t be a hard sell to introduce Kotlin this way 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.

FeatureLombokKotlinNote
Final local referencesvalvalval is a Kotlin keyword
Reassignable local referencesvarvarvar is a Kotlin keyword
Non-nullable references@NonNullNo keyword neededIn Kotlin, types are nonnullable by default, and need to be explicitly declared nullable with a question mark, ie String?.
Automatic Resource Management (ARM)@CleanupCloseable.useFor example:
FileInputStream("input.txt").use {
input -> // use
}
Create getters and setters@Getter/@SetterPart of data classes by declaring properties as var in data classesFor example:
data class Person(var name: String)
Creates a getter and setter of name on Person.
Create a toString@ToStringPart of data classesFor example:
data class Person(var name: String)
Creates an automatic toString on Person.
Create a equals and hashcode method@EqualsAndHashCodePart of data classesFor example:
data class Person(val name: String)
Creates an automatic equals and hashcode on Person.
Create a constructor without any arguments@NoArgsConstructorPart of data classes by giving all arguments a default value or by introducing a secondary constructorFor 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 @AllArgsConstructorPart of data classesFor example:
data class Person(val name: String)

Automatically creates a constructor for all parameters.
Create a mutable data class@DataPart of data classes by using ‘var’ in field declarationsFor example:
data class Person(var name: String)

Automatically creates the toString, hashCode, equals, etc.
Create an immutable data class@ValuePart of data classes by using ‘val’ in field declarationsFor example:
data class Person(val name: String)
Object creation using named properties@BuilderNamed arguments in KotlinPerson(name = "Sergey", age = 25)
Convert checked exceptions into unchecked exceptions@SneakyThrowsAll 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.@SynchronizedKotlin withLock method. It’s not completely the same, but it’s close. A better alternative is to look at Kotlin coroutinessomeLock.withLock {    
sharedResource.operation()
}
Lazy property initialization.@Getter(lazy=true)Delegated properties`by lazy`
Automatic logger@LogNo built-in alternative……but a marker interface would make this easy to implement.
Utility classes@UtilityClassMark a class a objectThis turns all methods into helper/static methods

As you can see in the table above, most features of Lombok are available in Kolin. What, however, is great about Lombok, is the flexibility it gives. For example, it’s easy to add a toString method to a class without adding a 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 which 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 (which is something we did) to Delombok the project, and slowly migrate to Kotlin. Which approach you take depends mostly on your project size, but for us, the easiest was delombok the project and convert it to Kotlin.

Conclusion

I hope the above guide helps in introducing Kotlin into your project. It should be a safe and readable conversion, which gives the basis to introduce 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!

Older Post
Newer Post

Leave a Reply

Your email address will not be published. Required fields are marked *