Turning idiomatic Java, into idiomatic Kotlin

Converting Java to Kotlin can be a tricky thing. The languages aren't a million miles from each other, and the Java-to-Kotlin converter provided by JetBrains does an admirable job, but direct conversions like these often fail to account for idiomatic differences. Just like human languages, programming languages have their own unique styles and ways of solving problems, and knowing how these contrast and resemble each other is an important skill to have.

In this post we'll look at some Java code using common coding patterns and idioms, and how we can fix the same problem in Kotlin, but in a way more encouraged and supported by the Kotlin language.

Matching on type

Sometimes you'll want to perform a different operation depending on the type of your value. Here we've got an object called Request with three subtypes (GetRequest, PostRequest and PatchRequest), and we want to call a different method depending on that type:

Java

public ValidationResult validateRequest(final Request request) {
    if (myObj instanceof GetRequest) {
        // in version 14+ the compiler will auto-cast types after an `instanceof` check
        // Otherwise we'd need to do it manually
        return validateGetRequest(request);
    } else if (animal instanceof PostRequest) {
        return validatePostRequest(request);
    } else if (animal instanceof PatchRequest) {
        return validatePatchRequest(request);
    }
}

Kotlin

fun validateRequest(request: Request): ValidationResult = when(request) {
    is GetRequest -> validateGetRequest(request)
    is PostRequest-> validatePostRequest(request)
    is PatchRequest-> validatePatchRequest(request)
}

Immutable data containers

Often, you'll want a data structure to hold some structured data without performing any (or much) logic, often to move data between processes or services. These are sometimes called DTOs (data transfer objects), or a tuple (especially in Python), but the phrase data class has been more popular recently.

Java

class HttpRequest {

    final Map<String, String> headers;

    final String body;

    final String verb;

    // Oof, so wordy
    public HttpRequest(final Map<String, String> headers, final String body, final String verb) {
        this.headers = headers;
        this.body = body;
        this.verb = verb;
    }

    public int getContentLength() {
        return this.body.length();
    }

    @Nullable
    public String getContentType() {
        return this.headers.get("Content-Type");
    }
}

Kotlin

// Much better!
data class HttpRequest(val headers: Map<String, String>, val body: String, val verb: String) {

    val contentLength: Int
        get() = body.length

    val contentType: String?
        get() = headers["Content-Type"]
}

Chaining null values

Null references have been called "The Billion Dollar Mistake" for the number of production problems it's caused. Many languages (including Java) have introduced techniques to deal with this, but unpacking a chain of nullable values can still lead to problems, or at the very least wordiness. Here we get an object that might be null, retrieve another value from it that might be null, and then a final possibly-null value.

Java

// Optionals let us avoid heaps of `if` null checks but it's still not great
var value = Optional.ofNullable(getPossiblyNullValue())
    .map(v1 -> v1.nextNullableValue())
    .map(v2 -> v2.finalNullableValue())
    .orElseGet(null);

Kotlin

var value = getPossiblyNullValue()?.nextNullableValue()?.finalNullableValue()