Kotlin: create REST services using Jersey and Jackson

Kotlin is a JVM language developed by the folks at Jetbrains.

It is a concise and compact language with 100% Java compatibility and 100% null-safety.

Another neat feature is its versatility: you can use the Kotlin language to create Java SE, Java EE, Android and JavaScript (Node.js) applications.

This blog will focus on using Kotlin to implement a simple Java SE application exposing a single REST endpoint using Jersey + Netty and Jackson.

  • First is an explanation of the syntax and highlights of Kotlin.
  • Second comes a short tutorial to illustrate how you can use Kotlin with Gradle to define your build script.
  • Third we look at a small tutorial of Jersey and how to fire it up by using a Netty HTTP server.
  • Fourth is a short explanation of how Jackson can be used in combination with Kotlin data classes.
  • Lastly, a small summary describes the created REST service and contains links to the used resources.

Exploring Kotlin

Syntax

The syntax of Kotlin is somewhere between Java and Scala. For example, the semicolon after a statement can be omitted.

Another thing to note is that you must specify whether the type is nillable. This is done by adding a question mark to the type, for example String versus String?.

The biggest difference, however, in comparison to Java is the way variables and methods are declared.

Declaring variables

When declaring a variable, you start with either val or var to denote if the variable is immutable or mutable.

After that follows the name and finally a colon with the type. An example of a simple String declaration:

val username: String

The type can be omitted if the value is directly assigned:
var username = "Gerard" // the deduced type is String

Note that the above variables can never contain the value null, because we didn’t specify that username is nillable.

The following example would cause a compiler error:

var username = "Gerard" // the deduced type is String
username = null // this causes a compiler error

To fix this we must explicitly specify that the type is nillable:
var username: String? = "Gerard"
username = null // this is allowed

Using a nillable type without first doing a null-check also causes a compiler error:
var username: String? = "Gerard"
val usernameLength = username.length // this causes a compiler error

To get the String’s length we must first validate that username is not null. This can be done with an if-statement, but luckily Kotlin also features a short-hand for doing null-checks:
var username: String? = "Gerard"
val usernameLength = if(username != null) username.length else null // this is allowed
val usernameLength = username?.length // even shorter

Note that because username is nillable the resulting type of usernameLength is also nillable. So the deduced type of usernameLenght is Int?.

Declaring methods

When declaring a method you start with the keyword fun followed by the method’s name. After that follows the arguments and finally the return type. An example of a simple getter:

fun getUsername(): String? {
    return username
}

Parameters in a method follow the same form of declaration as those of variables. An example of a simple setter is as follows:
fun setUsername(username: String) {
    this.username = username
}

Note that the return type is omitted, which causes the method return type to automatically be Unit. This is the Kotlin equivalent of void.

Data classes

Kotlin introduces a new type of class called a data class. The purpose of this type is to design a class that only holds data.

Usually the fields in these kind of classes are immutable to guarantee thread-safety. The Kotlin compiler will automatically generate the equals(), hashCode()toString(), and copy() methods, which saves a lot of boiler-plate code.

To illustrate this, let’s implement a simple User class with a username, password and age. Although the definition sounds simple, the resulting class written in Java is still 50 lines of code:

public class User {

    private final String username, password;
    private final int age;

    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        return username.equals(user.username) && password.equals(user.password) && age == user.age;
    }

    @Override
    public int hashCode() {
        int result = username.hashCode();
        result = 31 * result + password.hashCode();
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

Writing the same class in Kotlin only requires 1 line of code:
data class User(val username: String, val password: String, val age: Int)

Note that in Kotlin we do not have to implement any method at all, because the methods implemented in the Java example are automatically generated for us.

Collections

Kotlin adds a whole bunch of useful (extension) methods to collections, such as map, flatMap, filter and forEach.

Contrary to Scala these collections are fully compatible with the java.util collections. The methods that are added by Kotlin are similar to the methods found in a Java 8 Stream.

The main difference is that collections are always finite whereas a Stream can potentially be infinite.

Let’s assume that someone wants to have a list of all unique passwords. We could implement this in Java 7 using a simple for loop:

List<User> users = new ArrayList<>();
users.add(new User("Gerard", "secret", 25));
users.add(new User("FooBar", "secret", 36));
Set<String> uniquePasswords = new HashSet<>();
for(User user : users) {
    uniquePasswords.add(user.getPassword());
}

Or we could implement this in Java 8 using a Stream:

List<User> users = new ArrayList<>();
users.add(new User("Gerard", "secret", 25));
users.add(new User("FooBar", "secret", 36));
Set<String> uniquePasswords = users.stream().map(User::getPassword).collect(Collectors.toSet());

What I personally don’t like in this approach is that we must first convert the List to a Stream, then execute the map method, and finally collect the result in a Set.

Implementing the same in Kotlin is far more elegant and readable (also note the syntax for adding elements to a List):

val users = ArrayList<User>()
users += User("Gerard", "secret", 25)
users += User("FooBar", "secret", 36)
val uniquePasswords = users.map(User::password).toSet()

Companion objects

In Kotlin there is no static keyword. Instead you can define a companion object inside a class. To define a static constant inside a class, you do so inside the block companion object { ... }:

class ExampleClass {
    companion object {
        private val MY_CONSTANT_INT = 10
    }
}

The constant MY_CONSTANT_INT is now available to use inside ExampleClass.

If you omitted the private modifier it would also be available in other classes as ExampleClass.MY_CONSTANT_INT, just like it works in Java.

Companion objects can also contain functions, which behave like static functions in Java:

class ExampleClass {
    companion object {
        fun generateLuckyNumber(): Int {
            return java.util.Random().nextInt()
        }
    }
}

Singletons

There are situations in which it is desirable to have only 1 instance of a class at any given time. This is known as a singleton.

Implementing a singleton in Java requires a few tricks to prevent multiple instances from being created:

public final class ExampleSingleton {
    private static ExampleSingleton instance = new ExampleSingleton();

    public static ExampleSingleton getInstance() {
        return instance;
    }

    private ExampleSingleton() {
    }
    
    public void someCriticalMethod() {
    }
}

In this example, we first define a final class (to prevent other from extending this class). Then we create a single static instance using a private constructor to ensure there is only 1 instance.

Finally, we implement a getInstance() method to expose the created instance.

Luckily in Kotlin we have the special type object for these situations.

The singleton shown above can be reduced to the following with Kotlin:

object ExampleSingleton {
    fun someCriticalMethod() {}
}

Gradle & Kotlin

Before diving into implementing a simple REST service, first choose a build tool to manage the dependencies and build the application. I personally prefer Gradle because it offers a lot of flexibility and is compatible with Maven.

Since version 3.x it is also possible to write the build script using Kotlin, which is awesome!

In order to use Kotlin as the language for the build script, we specify an alternative build file in the settings.gradle file (inspired by this example):

rootProject.buildFileName = 'build.gradle.kts'
rootProject.name = 'kotlin-rest-service'

Another caveat is that you must opt-in to type-safe accessors, otherwise you will get compiler errors when using extension blocks such as application { ... }.

Read these release notes if you want to know why.

To do this, add the following line to the gradle.properties file:

org.gradle.script.lang.kotlin.accessors.auto=true

Now we can write a nice statically-typed build script to configure our project:
buildscript {
    repositories {
        gradleScriptKotlin()
    }
    dependencies {
        classpath(kotlinModule("gradle-plugin"))
    }
}

repositories {
    mavenCentral()
}

group = "nl.craftsmen.blog.kotlin"
version = "1.0.0-SNAPSHOT"

apply {
    plugin("kotlin")
}

plugins {
    application
}

application {
    mainClassName = "nl.craftsmen.blog.kotlin.NettyServer" // we will create this class later on in this blog
}

dependencies {
    compile(kotlinModule("stdlib"))
}

Jersey + Netty

Jersey is a RESTful web services framework that enables exposing REST resources using a few simple annotations.

It is also the reference JAX-RS implementation that is used in GlassFish.

Although Jersey is a web services framework it does not implement a web server/container. Instead it requires the user to provide a ContainerProvider that starts a web container which Jersey can hook into.

For this blog we will be using Netty as our HTTP server implementation, because it is widely used and Jersey already has an implementation of ContainerProvider for Netty.

Creating a REST resource

To add Jersey and Netty to the classpath, add the following lines to the dependencies block in the Gradle build script:

compile("org.glassfish.jersey.core:jersey-server:2.25.1")
compile("org.glassfish.jersey.containers:jersey-container-netty-http:2.25.1")

Let’s take the class User that we defined earlier:

data class User(val username: String, val password: String, val age: Int)

To expose a collection of User instances using a REST endpoint, first define a resource class:
import javax.ws.rs.DELETE
import javax.ws.rs.GET
import javax.ws.rs.POST
import javax.ws.rs.PUT
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType.APPLICATION_JSON

@Path("users")
@Produces(APPLICATION_JSON)
class UserResource {
    private val users = HashMap<String, User>()

    init {
        users += "Gerard" to User("Gerard", "secret", 25)
    }

    @GET @Path("{username}")
    fun getUser(@PathParam("username") username: String): User? {
        return users[username]
    }

    @POST
    fun createUser(user: User) {
        users += user.username to user
    }

    @PUT @Path("{username}")
    fun updateUser(@PathParam("username") username: String, user: User) {
        users -= username
        users += user.username to user
    }

    @DELETE @Path("{username}")
    fun deleteUser(@PathParam("username") username: String): User? {
        return users.remove(username)
    }
}

This class defines a HashMap of User instances with their username as the key.

During constructor initialization the init block is executed, adding one entry to the map.

If you paid attention you would’ve noticed that the method getUser() returns User?, which means it can be null.

This is on purpose because the map might not contain a user with the given username. Also note the various JAX-RS (method) annotations. These are explained below.

Used JAX-RS annotations:

  • @Path
    Defines the HTTP path that can be used to reach a resource.
    Values between curly braces must be bound to a method parameter using @PathParam.
    When used on a class the specified path is prefixed to all methods inside the class.
    When used on a method the specified path is relative to the path of the class annotation.
  • @Produces
    Specifies the media type that should be used in the HTTP response.
    Can be used on a class, in which case the media type is used for all methods.
  • @GET
    Binds an HTTP GET request on the specified path to the annotated method.
  • @POST
    Binds an HTTP POST request on the specified path to the annotated method.
    The body of the request is automatically bound to the first non-annotated parameter.
  • @PUT
    Binds an HTTP PUT request on the specified path to the annotated method.
    The body of the request is automatically bound to the first non-annotated parameter.
  • @DELETE
    Binds an HTTP DELETE request on the specified path to the annotated method.
  • @PathParam
    Binds a path parameter (value between curly braces) to a method parameter.

Exposing the REST resource

The UserResource class is exposed by defining a class that extends Jersey’s Application class:

import javax.ws.rs.core.Application

class JerseyApplication: Application() {
    override fun getSingletons(): MutableSet<Any> {
        return mutableSetOf(UserResource())
    }
}

This class overrides the parent method Set<Object> getSingletons(). It returns a set with a single instance of UserResource in it.

Now we can define a simple singleton object that starts the Netty server:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.glassfish.jersey.jackson.JacksonFeature
import org.glassfish.jersey.netty.httpserver.NettyHttpContainerProvider
import org.glassfish.jersey.server.ResourceConfig
import java.net.URI
import javax.ws.rs.ext.ContextResolver

object NettyServer {
    @JvmStatic
    fun main(args: Array<String>) {
        val resourceConfig = ResourceConfig.forApplication(JerseyApplication())
        val server = NettyHttpContainerProvider.createHttp2Server(URI.create("http://localhost:8080/"), resourceConfig, null)
        Runtime.getRuntime().addShutdownHook(Thread(Runnable { server.close() }))
    }
}

In this class a ResourceConfig is created based on our JerseyApplication class. Then a Netty server is created and started. It is exposed on http://localhost:8080/.

However, if we try to GET http://localhost:8080/users/Gerard we get an HTTP 500 response. This is because we specified that the content should be serialized to JSON, but we didn’t implement a serializer.

Instead of implementing the serialization ourselves we will use Jackson.

Jackson & data classes

Jackson is a commonly used framework for serializing Java objects into JSON and vice versa. It is one of the few serialization frameworks that can invoke an user-defined constructor. This is very useful because Kotlin data classes don’t have a no-arg constructor (unless every field has a default value).

JAXB for example requires that classes have a no-arg constructor, making it unsuitable for our needs.

Configuring Jackson

To use Jackson with Jersey we must register Jersey’s JacksonFeature on the ResourceConfig instance.

Fortunately Jersey will automatically register this feature if it’s on the classpath. To do this, add the following line to the Gradle build script:

compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.25.1")

If we try to GET http://localhost:8080/users/Gerard again we will get a JSON response!

Hooray!

Now lets add a new user by sending a POST request to http://localhost:8080/users with the following JSON:

{
	"username": "FooBar",
	"password": "secret",
	"age": 36
}

This will return an HTTP 400 response with the following body:
Can not construct instance of nl.craftsmen.blog.kotlin.User: 
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@45e2534; line: 2, column: 2]

But what happened? Although Jersey can now successfully serialize our User class using Jackson, it is unable to deserialize such a class.

This is caused by the fact that Jackson does not know how to invoke the constructor of a Kotlin data class.

Deserializing data classes

In order to deserialize Kotlin data classes we have to register Jackson’s KotlinModule on an ObjectMapper instance.

This module can be added to the classpath by adding the following line to the Gradle build script:

compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.4")

Jackson does not automatically register new modules, so we have to do this ourselves. Open the object NettyServer that we created earlier and replace the following line:

val resourceConfig = ResourceConfig.forApplication(JerseyApplication())

with this line:
val resourceConfig = ResourceConfig.forApplication(JerseyApplication())
        .register(ContextResolver<ObjectMapper> { ObjectMapper().registerModule(KotlinModule()) })

If we try the POST request again we will now get an HTTP 200 response. Sending a GET request to  http://localhost:8080/users/FooBar will now return the JSON we just POSTed.

It is possible to update the user information by sending a PUT request to http://localhost:8080/users/FooBar with the following JSON:

{
	"username": "FooBar",
	"password": "evenmoresecret",
	"age": 37
}

You can also delete an user by sending a DELETE request to http://localhost:8080/users/FooBar.

Summary

We have successfully created a simple REST service that can be used to create, read, update and delete user information. 

You can find the full code of the demo application at https://gitlab.com/craftsmen/kotlin-rest-service.

Also check out the following resources:

Leave a Reply

Your email address will not be published.