Ready to have fun? Try Kotlin and Spring!

Otavio Santana
Developer Relations
15 Nov 2019

Since our first Java in the cloud with Platform.sh series post, we talked about various frameworks and support within the Java world. Like integration with services and tools such as Spring and Jakarta EE/Eclipse MicroProfile. One important thing about Java? Its specifications. It’s worth remembering that the JVM by its specification does not read Java code, but bytecode. So within the JVM, it’s possible to run several languages. The purpose of this post is to present how the JVM can be multilingual using a very fashionable language—Kotlin.

Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. From the documentation: Kotlin is more concise. Rough estimates indicate approximately a 40 percent reduction in the number of lines of code. It’s also more type-safe, e.g., support for non-nullable types makes applications less prone to NPEs. Other features including smart casting, higher-order functions, extension functions, and lambdas with receivers provide the ability to write expressive code as well as facilitating the creation of DSL. Designed to interoperate fully with Java, the JVM version of Kotlin’s standard library depends on the Java Class Library, but type inference allows its syntax to be more concise.

Show me the code

To illustrate how Kotlin works, we’ll create a smooth CRUD application to store cities into a MySQL database. We’ll create a city CRUD with Spring Boot, where the City entity has three fields: name, country, and ID.

To create your first project with Kotlin, we’ll set Maven as build automation tools, and, as usual, we’ll set the dependencies. We’ll also need to set the plugin to enable Kotlin code in our project. As you can see below, inside the build tag, the Kotlin plugin compiles the Kotlin code.

<?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>sh.platform.template</groupId>
	<artifactId>template-spring-kotlin</artifactId>
	<version>0.0.1</version>

	<parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-parent</artifactId>
     	<version>2.2.0.RELEASE</version>
	</parent>

	<properties>
    	<jayway-rest-assured.version>2.9.0</jayway-rest-assured.version>
    	<kotlin.version>1.3.50</kotlin.version>
    	<httpclient.version>4.5.8</httpclient.version>
    	<platform.sh.version>2.2.2</platform.sh.version>
	</properties>

	<dependencies>
    	<dependency>
        	<groupId>sh.platform</groupId>
        	<artifactId>config</artifactId>
        	<version>${platform.sh.version}</version>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-data-jpa</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-web</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>com.fasterxml.jackson.module</groupId>
        	<artifactId>jackson-module-kotlin</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>org.jetbrains.kotlin</groupId>
        	<artifactId>kotlin-reflect</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>org.jetbrains.kotlin</groupId>
        	<artifactId>kotlin-stdlib-jdk8</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>mysql</groupId>
        	<artifactId>mysql-connector-java</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
	</dependencies>

	<build>
    	<finalName>spring-kotlin</finalName>
    	<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    	<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    	<plugins>
        	<plugin>
            	<groupId>org.springframework.boot</groupId>
            	<artifactId>spring-boot-maven-plugin</artifactId>
        	</plugin>
        	<plugin>
            	<artifactId>kotlin-maven-plugin</artifactId>
            	<groupId>org.jetbrains.kotlin</groupId>
            	<configuration>
                	<args>
                    	<arg>-Xjsr305=strict</arg>
                	</args>
                	<compilerPlugins>
                    	<plugin>spring</plugin>
                    	<plugin>jpa</plugin>
                    	<plugin>all-open</plugin>
                	</compilerPlugins>
                	<pluginOptions>
                    	<option>all-open:annotation=javax.persistence.Entity</option>
                    	<option>all-open:annotation=javax.persistence.Embeddable</option>
                    	<option>all-open:annotation=javax.persistence.MappedSuperclass</option>
                	</pluginOptions>
            	</configuration>
            	<dependencies>
                	<dependency>
                    	<groupId>org.jetbrains.kotlin</groupId>
                    	<artifactId>kotlin-maven-allopen</artifactId>
                    	<version>${kotlin.version}</version>
                	</dependency>
                	<dependency>
                    	<groupId>org.jetbrains.kotlin</groupId>
                    	<artifactId>kotlin-maven-noarg</artifactId>
                    	<version>${kotlin.version}</version>
                	</dependency>
            	</dependencies>
            	<executions>
                	<execution>
                    	<id>kapt</id>
                    	<goals>
                        	<goal>kapt</goal>
                    	</goals>
                    	<configuration>
                        	<sourceDirs>
                            	<sourceDir>src/main/kotlin</sourceDir>
                        	</sourceDirs>
                        	<annotationProcessorPaths>
                            	<annotationProcessorPath>
                                	<groupId>org.springframework.boot</groupId>
                                	<artifactId>spring-boot-configuration-processor</artifactId>
                                	<version>${project.parent.version}</version>
                            	</annotationProcessorPath>
                        	</annotationProcessorPaths>
                    	</configuration>
                	</execution>
            	</executions>
        	</plugin>
    	</plugins>
	</build>
</project>

The configuration is ready. The first step in the code will be the Entity class: City. Kotlin has the data class, whose primary purpose is to hold data, such a class’s standard functionality; utility functions are often mechanically derivable from the data.

The scope of this data class is to compile and generate several methods, such as equals, hashCode, and ToString automatically to you.

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
data class City(
    	var name: String = "",
    	var country: String = "",
    	@Id @GeneratedValue var id: Long? = null)

To make the integration between the data class entity and the database go smoothly, we’ll use the Spring Data repository interface.

import org.springframework.data.repository.CrudRepository

interface CityRepository : CrudRepository<City, Long> {
}

The last frontier between the code and the HTTP request is the controller class. As you can see, the CityController is pretty similar to the Java class. At the constructor of the CityController, we set the repository as a read-only variable you can see with the `val´ keyword. Each function has the ´fun´ keyword, and there are several ways to create it; you can choose any function that you want—as long as you have fun with it!

import org.springframework.web.bind.annotation.*

@RestController
class CityController(private val repository: CityRepository) {

	@GetMapping("cities")
	fun findAll(): Iterable<City> {
    	return repository.findAll()
	}

	@GetMapping("cities/{id}")
	fun getDeveloper(@PathVariable id: Long): City =
        	repository.findById(id).orElseThrow { IllegalArgumentException("City does not exist") }

	@DeleteMapping("cities/{id}")
	fun delete(@PathVariable id: Long) {
    	repository.deleteById(id)
	}

	@PostMapping("cities")
	fun add(@RequestBody city: City) {
    	repository.save(city)
	}
}

Kotlin and Platform.sh

The DataSourceConfig class does a tight integration between Platform.sh and Spring. This class reads information from Platform.sh and returns DataSource to be available for use in the Spring container. To make the integration between Java code and Platform.sh more accessible, we have the Java Config reader, with the possibility of mixing Java and Kotlin code in the same project.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import sh.platform.config.Config
import sh.platform.config.MySQL
import javax.sql.DataSource

@Configuration
class DataSourceConfig {

	@Bean(name = ["dataSource"])
	fun getDataSource(): DataSource {
    	val config = Config()
    	val database = config.getCredential("database") { MySQL(it) }
    	return database.get()
	}
}

Platform.sh structure

The Java application is ready to go! The next step is to set the Platform.sh files required to manage and deploy the demo. Every application you deploy on Platform.sh is built as a virtual cluster, containing a set of containers. There are three types of containers within your cluster:

  • One Router (.platform/routes.yaml). Platform.sh allows you to define the routes.
  • Zero or more service containers (.platform/services.yaml). Platform.sh allows you to completely define and configure the topology and services you want to use on your project.
  • One or more application containers (.platform.app.yaml). You control your application and the way it will be built and deployed on Platform.sh via a single configuration file.

The services file allows you to define the services, such as database, search engine, cache, and so on; in this project demo, it will be a MySQL instance. The configuration files are the same as in the first post.

The application is now ready, so it’s time to move it to the cloud with Platform.sh using the following steps:

  • Create a new free trial account.
  • Sign up with a new user and password, or login using a current GitHub, Bitbucket, or Google account. If you use a third-party login, you’ll be able to set a password for your Platform.sh account later.
  • Select the region of the world where your site should live.
  • Select the blank template.

After this wizard, Platform.sh will provision the whole infrastructure to you and will offer a Git remote repository. Before access, remember to set the SSH keys. The Platform.sh Git-driven infrastructure means it will automatically manage everything your application needs to push it to the master remote repository. You only need to write your code—including a few YAML files that specify your desired infrastructure—then commit it to Git, and push.

git remote add platform <platform.sh@gitrepository>
git commit -m "Initial project"
git push -u platform master

Code pushed will create both the Java application and a MySQL instance, and, when it’s done, will return an IP address to the service.

In this post demonstrating Kotlin running in the cloud with Platform.sh, we compared Kotlin code with Java code—and you can now see how Kotlin is more simple in some aspects, using Java as the data-class type. Looking forward to more language clarity within the cloud JVM? Stay tuned for more news soon!