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: