Coming Up for Air

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:

  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:

 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.

Search

    Quotes

    Sample quote

    Quote source

    About

    My name is Jason Lee. I am a software developer living in the middle of Oklahoma. I’ve been a professional developer since 1997, using a variety of languages, including Java, Javascript, PHP, Python, Delphi, and even a bit of C#. I currently work for Red Hat on the WildFly/EAP team, where, among other things, I maintain integrations for some MicroProfile specs, OpenTelemetry, Micrometer, Jakarta Faces, and Bean Validation. (Full resume here. LinkedIn profile)

    I am the president of the Oklahoma City JUG, and an occasional speaker at the JUG and a variety of technical conferences.

    On the personal side, I’m active in my church, and enjoy bass guitar, running, fishing, and a variety of martial arts. I’m also married to a beautiful woman, and have two boys, who, thankfully, look like their mother.

    My Links

    Publications