1. Overview
A big announcement was made back in January in the Spring ecosystem: Kotlin support is coming to Spring Framework 5. This means that Spring Boot 2.x will have first class support for Kotlin.
This is of course not unexpected, as the team at Pivotal is known for the acceptance of JVM languages such as Scala and Groovy.
Let’s build a Kotlin app using Spring Boot app 2.x!
2. Setup
2.1. Environment
Kotlin supports development in IntelliJ, Eclipse, and on the command line. Follow the directions to set up your environment, based on your preferences.
2.2. Setup
First, let’s create a Spring Boot 2 project and modify the POM to contain entries specifying the versions of Java and Kotlin with the dependencies:
<dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jre8</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> <version>1.1.2</version> </dependency>
Take note that we are specifying file locations for our Kotlin source and test files:
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
If our Kotlin files are in different locations, you will need to modify these entries in the POM.
To compile Kotlin modules and sources, we need to use kotlin-maven-plugin:
<plugin> <artifactId>kotlin-maven-plugin</artifactId> <groupId>org.jetbrains.kotlin</groupId> <version>1.1.2</version> <configuration> <compilerPlugins> <plugin>spring</plugin> </compilerPlugins> <jvmTarget>1.8</jvmTarget> </configuration> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>1.1.2</version> </dependency> </dependencies> </plugin>
Alright, now we have everything we need to build our Kotlin application. For reference: you can find the latest versions of Maven Central (spring-boot-starter-web, kotlin-stdlib-jre8, kotlin-reflect, jackson-module-kotlin, test).
Next, let’s set up our application context.
3. Application Context
Let’s jump into some Kotlin code and write our familiar Spring Boot application context:
@SpringBootApplication class KotlinDemoApplication fun main(args: Array<String>) { SpringApplication.run(KotlinDemoApplication::class.java, *args) }
We see our familiar @SpringBootApplication annotation. This is the same annotation we would use in a Java class.
Below that we have a class definition for our KotlinDemoApplication class. In Kotlin the default scope for classes is public so that we can omit that. Additionally, if a class has no variables and no functions, it can be declared without curly braces. So, in essence, we have just defined a class.
Moving on to the method. This is the standard Java entry point method, in Java: public static void main(String[] args).
Again, methods or functions are public by default, so we do not have to declare that here. Additionally, functions that do not return anything do not need to specify a void return type.
And finally, any function defined outside the body of a class is automatically static. This makes this function eligible for startup execution.
Now let’s run our application from the root directory using mvn spring-boot: run. The application should start, and we should see our application running on port 8080.
Next, let’s build a controller.
4. Controller
Let’s take a look at adding a controller to our service:
@RestController class HelloController { @GetMapping("/hello") fun helloKotlin(): String { return "hello world" } }
Not too much different from a standard Spring controller but certainly less code. Let’s add a test class and case for this controller to validate our work:
@RunWith(SpringRunner::class) @SpringBootTest(classes = arrayOf(KotlinDemoApplication::class), webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class KotlinDemoApplicationTests { @Autowired lateinit var testRestTemplate: TestRestTemplate @Test fun whenCalled_shouldReturnHello() { val result = testRestTemplate // ... .getForEntity("/hello", String::class.java) assertNotNull(result) assertEquals(result?.statusCode, HttpStatus.OK) assertEquals(result?.body, "hello world") } }
This test shows off one of Kotlin’s very powerful features – null safety! Kotlin variables that can be null must be declared using ‘?’. The compiler then knows that defensive coding is required before accessing that property.
In our test, TestRestTemplate is defined as a nullable type, and each time we access it we do so using the null coalescing operator “?.” – which will return null if the called object is null.
This clarifies the use of nulls in the program and forces developers to write safe code when working with them.
Next, let’s add a service and integrate that into our controller.
5. Service
As you can probably guess now, our service is going to be pretty easy to add into our project. Let’s do that now:
@Service class HelloService { fun getHello(): String { return "hello service" } }
Pretty simple service here with a single function returning a String. Next, let’s wire our service into the controller and use it to return a value:
@RestController class HelloController(val helloService: HelloService) { // ... @GetMapping("/hello-service") fun helloKotlinService(): String { return helloService.getHello() } }
Ahh, that looks nice! In Kotlin the main constructor can be defined inline with the class declaration. We’ve omitted the @Autowired annotation from our constructor because it’s not obligatory since some time.
Those parameters are automatically converted to fields in the class. Kotlin they are called properties. There are no getters or setters defined; they are created automatically. You can, of course, override these defaults if you want.
In Kotlin, properties in classes and variables in functions can be defined using var or val. Var indicates a mutable property, and val indicates a final one. This allows the compiler to check for illegal access. Since our HelloService is a singleton, we wire it up as a val to prevent mutation.
Next, let’s add a test for this controller method:
@Test fun whenCalled_shouldReturnHelloService() { var result = testRestTemplate // ... .getForEntity("/hello-service", String::class.java) assertNotNull(result) assertEquals(result?.statusCode, HttpStatus.OK) assertEquals(result?.body, "hello service") }
Lastly, let’s look at what a POJO looks like in Kotlin.
6. Kotlin Data Class
In Java, we represent data objects with plain old Java objects, the POJO. In Kotlin we have something that lets us express this type of object more concisely – a data class.
Let’s write a data object to return in our controller:
data class HelloDto(val greeting: String)
That was no trick. I’m not omitting anything from our class. With the data modifier, we get a lot of benefits. This keyword automatically creates an equals/hashcode pair, a toString function, and a copy function. All that from a 53 character one-liner!
Now let’s add a method to return our new data class:
// ... @GetMapping("/hello-dto") fun helloDto(): HelloDto { return HelloDto("Hello from the dto") }
The data modifier does not add a default constructor, which is important for certain libraries like Jackson. To support this type of class we have added the jackson-module-kotlin to our POM file to support marshaling. This was done during section 2, and you can see the dependency there.
Finally, let’s add a test for this controller function:
@Test fun whenCalled_shoudlReturnJSON() { val result = testRestTemplate // ... .getForEntity("/hello-dto", HelloDto::class.java) assertNotNull(result) assertEquals(result?.statusCode, HttpStatus.OK) assertEquals(result?.body, HelloDto("Hello from the dto")) }
7. Conclusion
In this article, we looked at Kotlin support in Spring Boot 2.x. We saw from examples that Kotlin could simplify and enhance our applications by forcing us to write shorter, safer code.
Kotlin also supports some amazing features like the data class, class extensions, and is fully compatible with existing Java code. This means that you can write Kotlin code and call it from your Java classes and vise-versa. In addition, Kotlin was built from the ground up to have fantastic support in an IDE, and it does.
There are a lot of reasons to try Kotlin out, and with Google and Spring backing it, now is the time to check it out. Let us know what you decided to build using it!
Always, you can find the source code over on GitHub.