Kotlin-RS
Tuesday, November 03, 2015 |In keeping with theme of "use existing frameworks with Kotlin" and misleading titles, here’s a quick and dirty demonstration of writing JAX-RS applications using Kotlin.
For those that read my Kotlin Faces post, the pom.xml for the project will look very familiar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.steeplesoft</groupId>
<artifactId>Kotlin-RS</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Kotlin-RS</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.0.0-beta-1038</kotlin.version>
</properties>
<repositories>
<repository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
For our REST service, we have a single endpoint that returns (dummy) information on books. Let’s take a look at our model first:
1
2
3
data class Book(var name : String, var description : String) {
constructor() : this("", "")
}
Unbelievably verbose, isn’t it? :) There are a few things going on here:
-
Using Kotlin’s very concise class declaration syntax, we are declaring a class,
Book
, which has two properties,name
anddescription
. We get the getters and setters for free since we declared the properties using thevar
keyword. -
We’re using Kotlin’s data class feature, which gets us several things (like
equals()
/hashCode()
andtoString()
) for free. -
Since we’re defining this as a data class, we must have at least one primary constructor argument. However, for JAX-RS' built-in serialization/deserialization support, we need a no-args constructor, so we define a secondary constructor, using the
constructor
keyword, which delegates back to the primary.
Next up is the resource itself:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Path("/books")
class BookResource {
@GET
fun getBooks(): Array<Book> {
return arrayOf(
Book("Book 1", "Book 1"),
Book("Book 2", "Book 2"),
Book("Book 3", "Book 3"))
}
@GET
@Path("{id}")
fun getBook(@PathParam("id") id: String): Book {
return Book("Book " + id, "Description " + id)
}
}
Kotlin syntax aside, this should look very familiar. We’re using Java annotations seamlessly,
just as one would expect to see them in Java code. The method implementations themselves are very
simple, demonstrating the conciseness of Kotlin’s collections support. Note also that creating
class instances in Kotlin does not require the new
keyword. Attempts to use it will result in a
compilation error. Also note that semicolons are not used as line endings. Attempts to use them
will result in a compilation error. :)
Finally, let’s take a look at the JAX-RS Application
class:
1
2
3
4
5
6
7
8
@ApplicationPath("resources")
class MyApplication : Application() {
override fun getClasses(): MutableSet<Class<*>>? {
val classes = HashSet<Class<*>>()
classes.add(BookResource::class.java)
return classes
}
}
This class was the trickiest, as it requires direct Java interop. JAX-RS developers are
likely familiar with Application.getClasses()
. The tricky part here is satisfying this
requirement in Kotlin, with the magic incantation being JavaClass::class.java
. I can’t
find this documented anywhere, so I can’t give a good explanation for it. I was given this
tip by my brother, so feel free to pester him. :) Maybe a Kotlin dev
will stumble across this and explain it in the comments.
UPDATE: Documentation for ::class.java
found here.
And, like I said list time, that’s it. Build the app (mvn package
) and deploy to your
favorite container and see it in all of its glory:
1
2
3
4
5
$ curl -H 'Accept: application/json' http://localhost:8080/Kotlin-RS-1.0-SNAPSHOT/resources/books
[{"description":"Book 1","name":"Book 1"},{"description":"Book 2","name":"Book 2"},{"description":"Book 3","name":"Book 3"}]
$ curl -H 'Accept: application/json' http://localhost:8080/Kotlin-RS-1.0-SNAPSHOT/resources/books/4
{"description":"Description 4","name":"Book 4"}
With a couple of minor caveats, it’s all very straightforward, and very nice. We get all of the benefits of a modern JVM languague without having to learn a whole new ecosystem.