Web Architecture, Java Ecosystem, Software Craftsmanship

Kotlin. The Java Ecosystem deserves this Language.

Posted on Nov 20, 2016

We at Spreadshirt have started to use the JVM language Kotlin in a couple of services. This ended up in great enthusiasm. Kotlin allows us to significantly reduce the boilerplate and to write more robust and readable code. In fact, I don’t want to write Java anymore. In this post I like to show you why.

Kotlin Logo

TL;DR

  • Kotlin is a statically-typed JVM language developed by JetBrains. JetBrains is the company behind IntelliJ IDEA.
  • Kotlin is a pragmatic language. It addresses problems that every Java developer faces every day without introducing significant complexity.
  • Introducing Kotlin comes with low risks. Kotlin is easy to learn, you stay in the familiar Java ecosystem and you can start using Kotlin alongside with Java.
  • Kotlin is fully interoperable with Java and existing Java frameworks
  • What are the benefits of using Kotlin?
    • Concise syntax and extremely reduced boilerplate (data classes, type inference, default parameter, single expression functions, compact collection API, null-safe calls, elvis operator, no checked exceptions)
    • Readability (named arguments, reduced boilerplate)
    • Less error-prone (compiler enforced null safety; named arguments; immutability; toString(), hashCode(), equals() don’t have to be manually maintained in data classes)

Evaluation

The introduction of a new programming language can be a long-term commitment. It may have great impact on both the software and the organization. So you should act with sound judgment. That’s why I like to share our evaluation of Kotlin.

Pros

  • Increased productivity
  • Less error-prone than Java
  • Reuse of the powerful and well-known Java ecosystem (JRE, JIT compiler, JMX, libraries, frameworks, best practices, build tools, dependency management, test, CI, CD, artifact repository, deployment, monitoring).
  • Interoperability with Java.
  • Easy to learn. No paradigm shift.
  • Stepwise migration possible. You can use Kotlin and Java at the same time within a project.
  • Increased attractiveness for applicants. Modern languages attract skilled developers.
  • Brilliant IDE support with IntelliJ IDEA.

Cons

  • Further development of the language depends on JetBrains.
  • Support for other IDEs (like Eclipse) is not as mature as IntelliJ.
  • Less documentation available compared with Java.

Conclusion

All in all, we claim that introducing Kotlin comes with low risks. Kotlin is easy to learn, you stay in the familiar Java ecosystem and you can start using Kotlin alongside with Java.

Code Examples: Java vs Kotlin

This is not going to be an extensive introduction to all features of Kotlin. For this, check out the Kotlin reference or Kotlin Idioms. The latter contains a brief overview of the language features. In the following I just like to point out some of my highlights. You can find the complete source code on my GitHub repository.

Example 1: Define and Map Beans

Kotlin shines when it comes to defining data classes and mapping between them. Let’s say we want to model a blog, its posts and comments. Moreover, we need the classes in two flavors: One class set for the persistence layer (postfix “*Entity”) and one for the JSON layer that are returned by our REST resources (postfix “*DTO”). Hence, we need to map between them. This is a usual approach for REST services.

The definition of the classes BlogEntity, PostEntity, AuthorEntity, CommentEntity, BlogDTO and PostDTO in Java is extremely verbose. We have to write a lot of boilerplate with constructors and setters.

Happy scrolling! :-)

//BlogEntity (received from persistence layer) map to BlogDTO (returned by our REST Service)
//Struct definition and mapping code are extremely verbose in java!
//Besides, null handling is cumbersome. Especially when it comes to nested objects that can be null.
//All values can be null. It's easy to run into NullPointerExceptions. This leads to error-prone code. And even if you add null-checks, it's easy to forget a check (because the compiler doesn't help you) and the code becomes very verbose.
//Argument lists are hard to read and error prone
class BlogEntity {
    private long id;
    private String name;
    private List<PostEntity> posts;

    //Praise my IDE for generating the constructor and getter boilerplate. Otherwise, I would drive nuts.
    //Moreover, equals(), hashCode(), toString() are still missing!
    //AND you have to maintain these methods when field are added or removed.
    public BlogEntity(long id, String name, List<PostEntity> posts) {
        this.id = id;
        this.name = name;
        this.posts = posts;
    }

    public String getName() {
        return name;
    }

    public List<PostEntity> getPosts() {
        return posts;
    }

    public long getId() {
        return id;
    }
}

class PostEntity {
    private long id;
    private AuthorEntity author;
    private Instant date;
    private String text;
    private List<CommentEntity> comments;

    public PostEntity(long id, AuthorEntity author, Instant date, String text, List<CommentEntity> comments) {
        this.id = id;
        this.author = author;
        this.date = date;
        this.text = text;
        this.comments = comments;
    }

    public AuthorEntity getAuthor() {
        return author;
    }

    public Instant getDate() {
        return date;
    }

    public String getText() {
        return text;
    }

    public List<CommentEntity> getComments() {
        return comments;
    }

    public long getId() {
        return id;
    }
}

class AuthorEntity {
    private String name;
    private String email;

    public AuthorEntity(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

class CommentEntity {
    private String text;
    private AuthorEntity author;
    private Instant date;

    public CommentEntity(String text, AuthorEntity author, Instant date) {
        this.text = text;
        this.author = author;
        this.date = date;
    }

    public String getText() {
        return text;
    }

    public AuthorEntity getAuthor() {
        return author;
    }

    public Instant getDate() {
        return date;
    }
}

class BlogDTO{
    private long id;
    private String name;
    private List<PostDTO> posts;

    public BlogDTO(long id, String name, List<PostDTO> posts) {
        this.id = id;
        this.name = name;
        this.posts = posts;
    }

    public String getName() {
        return name;
    }

    public List<PostDTO> getPosts() {
        return posts;
    }

    public long getId() {
        return id;
    }

}
class PostDTO{
    private long id;
    private String date;
    private String author;
    private String text;
    private String commentsHref;

    public PostDTO(long id, String date, String author, String text, String commentsHref) {
        this.id = id;
        this.date = date;
        this.author = author;
        this.text = text;
        this.commentsHref = commentsHref;
    }

    public String getAuthor() {
        return author;
    }

    public String getDate() {
        return date;
    }

    public String getText() {
        return text;
    }

    public String getCommentsHref() {
        return commentsHref;
    }

    public long getId() {
        return id;
    }
}

In Kotlin we can use data classes. They reduce our entity definition to a one-liner.

//Data classes: Each entity definition in a single line! We get immutability, constructor, hashCode(), equals(), toString() for free.
class BlogEntity(val id: Long, val name:String, val posts: List<PostEntity>?)
class PostEntity(val id: Long, val date: Instant?, val author: AuthorEntity?, val text: String, comments: List<CommentEntity>?)
class AuthorEntity(val name: String, val email: String?)
class CommentEntity(val text: String, val author: AuthorEntity, date: Instant)
//"val" makes the beans immutable ("var" fields can be modified).
//The type "String" can never be null. The compiler enforces this!
//Contrarily, the type "String?" is nullable. Null-safe types make the code much safer and avoid bloating null checks.

class BlogDTO(val id: Long, val name:String, val posts: List<PostDTO>?)
class PostDTO(val id: Long, val author: String, val date: String?, val text: String, commentsHref: String)

Next, we want to map the entity classes to their DTO counterparts. The Java code looks like this:

class Mapper {
    public List<BlogDTO> mapToBlogDTOs(List<BlogEntity> entities){
        //verbose stream api
        return entities.stream()
                .map(this::mapToBlogDTO)
                .collect(Collectors.toList());
    }
    private BlogDTO mapToBlogDTO(BlogEntity entity){
        return new BlogDTO(
                entity.getId(),
                entity.getName(),
                mapToPostDTO(entity.getPosts())
        );
    }

    private List<PostDTO> mapToPostDTO(List<PostEntity> posts) {
        //what if posts is null?! very unsafe code!
        return posts.stream()
                .map(this::mapToPostDTO)
                .collect(Collectors.toList());
    }

    private PostDTO mapToPostDTO(PostEntity post) {
        //what if author, date, text or comments are null?! error-prone code!
        //easy to mess up parameter order (most of them are strings). hard to understand meaning of last parameter.
        return new PostDTO(
                post.getId(),
                post.getDate() != null ? post.getDate().getEpochSecond()+ "" : null, //hard to read. easy to forget null check.
                getNameOrDefault(post.getAuthor()),
                post.getText(),
                "posts/" + post.getId() + "/comments"
        );
    }

    //null checks bloat code. very verbose. hard to read.
    private String getNameOrDefault(AuthorEntity author) {
        if (author != null){
            String name = author.getName();
            if (name != null){
                return name;
            }
        }
        return "Anonymous";
    }
}

Contrarily, the Kotlin code is much more concise:

//Usage of "single expression function": No body {} is necessary, if there is only a single expression.
//Less boilerplate with the collection API: we can call map() directly on a list and it returns a list.
//Implicit single parameter "it" makes lambda syntax even shorter. but you can also write "para -> mapToBlogDTO(para)".
fun mapToBlogDTOs(entities: List<BlogEntity>) = entities.map { mapToBlogDTO(it) }
fun mapToBlogDTO(entity: BlogEntity) = BlogDTO(
        id = entity.id,
        name = entity.name,
        posts = entity.posts?.map { mapToPostDTO(it) }
        //The Kotlin compiler forces me to consider that posts can be null.
        //We can't call map() directly on posts, because it can be null.
        //The null-safe call ("?.") invokes the operation only if posts are not null. Otherwise the whole expression is null.
)
fun mapToPostDTO(entity: PostEntity) = PostDTO(
        //easy to read due to named arguments.
        id = entity.id,
        date = entity.date?.epochSecond.toString(), //"?." (null safe call). if date is null, null is assigned. Otherwise the epochSecond is retrieved and assigned.
        author = entity.author?.name ?: "Anonymous", //The elvis operator ("?:") makes Java's getNameOrDefault() a one-liner! If left side of "?:" is null, the right side is returned. Otherwise the left side is returned.
        text = entity.text,
        commentsHref = "posts/${entity.id}/comments" //String interpolation!
)

All in all, Kotlin significantly reduces the boilerplate. In Java we have to write 201 lines of code. In Kotlin only 18 lines! 18! That’s awesome! That’s a factor of 11+ when it comes to structs and mapping. And we didn’t even added hashCode(), equals() and toString() in the Java code. Moreover, due to Kotlin’s built-in null-safety our code is less error-prone. And finally, the code is more readable due to named arguments and the reduced boilerplate.

Example 2: Conditions and Type Switches

My second highlight is the when statement of Kotlin. It’s much more than just a switch statement, because it supports many different conditions (value checks, ranges, multiple values, type checks).

Let’s say we want to get the default locale for a given delivery area. Java:

public Locale getDefaultLocale(String deliveryArea){
    if (deliveryArea.equals("germany") || deliveryArea.equals("austria") || deliveryArea.equals("switzerland")) {
        return Locale.GERMAN;
    }
    if (deliveryArea.equals("usa") || deliveryArea.equals("great britain") || deliveryArea.equals("australia")) {
        return Locale.ENGLISH;
    }
    throw new IllegalArgumentException("Unsupported deliveryArea " + deliveryArea);
}
//or via switch and fall-through

In Kotlin, we can use the powerful when construct:

fun getDefaultLocale(deliveryArea: String): Locale {
    when (deliveryArea){
        "germany", "austria", "switzerland" -> return Locale.GERMAN
        "usa", "great britain", "australia" -> return Locale.ENGLISH
        else -> throw IllegalArgumentException("Unsupported deliveryArea $deliveryArea") //string interpolation
    }
}
// or even shorter as a single expression function:
fun getDefaultLocale2(deliveryArea: String) = when (deliveryArea){
    "germany", "austria", "switzerland" -> Locale.GERMAN
    "usa", "great britain", "australia" -> Locale.ENGLISH
    else -> throw IllegalArgumentException("Unsupported deliveryArea $deliveryArea")
}

You can also use when as a type switch. Let’s assume that we want to behave differently depending on the type of a class. Java code:

//verbose. annoying type cast.
public String getExceptionMessage(Exception exception){
    if (exception instanceof MyLabeledException){
        return ((MyLabeledException) exception).getLabel();
    } else if (exception instanceof SQLException){
        return exception.getMessage() + ". state: " + ((SQLException) exception).getSQLState();
    } else {
        return exception.getMessage();
    }
}

class MyLabeledException extends RuntimeException{
    private String label;

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }
}

In Kotlin, a type checks are legal conditions within a when:

fun getExceptionMessage(exception: Exception) = when (exception){
    //concise type switches
    is MyLabeledException -> exception.label //smart cast to MyLabeledException -> we can call label directly.
    is SQLException -> "${exception.message}. state: ${exception.sqlState}" //string interpolation
    else -> exception.message
}
class MyLabeledException(val label: String) : RuntimeException(label)

Please note, that you don’t have to explicitly cast the parameter exception to MyLabeledException or SQLException. The Kotlin compiler notices the type check and auto-cast the type for us. This cool feature is called smart-cast.

Further Reading

Got hooked? I wrote a post about Best Practices in Kotlin and Idiomatic Kotlin and Kotlin in conjunction with Spring Boot and Vaadin. Check them out!