Coming Up for Air

Kotlin-RS

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:

<?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:

data class Book(var name : String, var description : String) {
    constructor() : this("", "")
}

Unbelievably verbose, isn’t it? :) There are a few things going on here:

  1. Using Kotlin’s very concise class declaration syntax, we are declaring a class, Book, which has two properties, name and description. We get the getters and setters for free since we declared the properties using the var keyword.

  2. We’re using Kotlin’s data class feature, which gets us several things (like equals()/hashCode() and toString()) for free.

  3. 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:

@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:

@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:

$ 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.

tags: Kotlin JAX-RS

Quotes

Sample quote

Quote source