DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Realistic Test Data Generation for Java Apps
  • Spring Microservice Tip: Abstracting the Database Hostname With Environment Variable
  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)

Trending

  • Issue and Present Verifiable Credentials With Spring Boot and Android
  • Java Virtual Threads and Scaling
  • Understanding Java Signals
  • Develop a Reverse Proxy With Caching in Go
  1. DZone
  2. Coding
  3. Languages
  4. Deploying a Kotlin App to Heroku

Deploying a Kotlin App to Heroku

In this post, we'll take a quick look at how to develop an app in Kotlin. We'll build a simple API with a PostgreSQL database and deploy it to Heroku to see it live.

By 
Michael Bogan user avatar
Michael Bogan
DZone Core CORE ·
Oct. 13, 21 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
6.7K Views

Join the DZone community and get the full member experience.

Join For Free

Since its earliest release, Java has touted itself as a "write once, run everywhere" programming language. The idea was that a programmer could develop an app in Java, have it compiled down to bytecode, and become an executable that can run on any platform, regardless of operating system or platform. It was able to do so in part by a runtime known as the Java Virtual Machine, or JVM. 

To Java's credit, the JVM was (and still is!) an incredibly fine-tuned runtime that abstracted away a computer's underlying hardware. While Java as a programming language survives to this day, it is often viewed as cumbersome, stodgy, and representative of an outdated approach to implementing solutions. 

In the last 10 years, more and more languages that run on the JVM have developed, but look and feel nothing like Java. One such language is Kotlin. Because of the JVM, it has no real performance advantages over regular Java. Still, its strength lies in the fact that it prioritizes legibility in a way that Java does not. Consider, for example, printing a substring in Java:

Java
 
// Java
String input = "What is the answer to the Ultimate Question of Life, the Universe, and Everything? 42";
String answer = input.substring(input.indexOf("?") + 1);
System.out.println(answer);

You must first get the index of the character you want to be in the substring, add one (since strings are zero-indexed), and call System.out.println to write to stdout.

In Kotlin, this is much shorter:

Java
 
// Kotlin
val input = "What is the answer to the Ultimate Question of Life, the Universe, and Everything? 42"
val answer = input.substringAfter("?")
println(answer)

Kotlin has garnered so much interest that Google even recommends it over Java for developing Android apps. 

In this post, we'll take a quick look at how to develop an app in Kotlin. We'll build a simple API with a PostgreSQL database and deploy it to Heroku to see it live.

Prerequisites

Before we begin, you'll need to make sure you've got the following software installed on your machine: 

  1. An account on Heroku. This is completely free and doesn't require any payment information.
  2. The Heroku CLI. Once your application is on Heroku, this will make managing it much easier.
  3. You'll need to have Kotlin installed (>= 1.4).
  4. You'll also need Gradle installed (>= 7.0).

You will also need to be a little familiar with Git and have it installed on your machine.

We’re going to be using the IntelliJ IDE for this Kotlin app. Their documentation provides some guidance on how to create a new project. Make sure you select the following options:

  • We want to create a Kotlin application that uses Gradle as a build system
  • Set the name of the project to kotlin-api
  • Set the JDK version to 16. If you don’t have this version installed, you can select Download JDK… from the dropdown, then choose Oracle Open JDK version 16

After the IDE sets everything up, you should have a directory structure that looks roughly like this:

kotlin-api
├── build.gradle.kts
└── src
├── main
    │   ├── kotlin

Our Kotlin files will be kept in src/main/kotlin, and our build logic will be in build.gradle.kts.

Getting started

Gradle is a build tool for a variety of languages. It also acts as a dependency management tool, similar to Maven. You’ll already have some lines in your build.gradle.kts file, which the IDE automatically added to be helpful. You can delete all of that, and paste in these lines instead:

Java
 
plugins {
    id("java")
    id("org.jetbrains.kotlin.jvm") version "1.5.10"
    id("org.springframework.boot") version "2.4.3"

    id("io.spring.dependency-management") version "1.0.11.RELEASE"
}

group "com.example"
version "0.0.1"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib") 

    implementation("org.springframework.boot:spring-boot-starter-web")    
    implementation("org.springframework.boot:spring-boot-starter")

    developmentOnly("org.springframework.boot:spring-boot-devtools")
}

These lines specify our project's dependencies and where to find them. For example, we want to use org.springframework.boot at version 2.4.3, which is why it's defined within the plugins block. We point out the repositories where the packages can be found—at mavenCentral()—and which exposed classes we want to use (implementation( "org.springframework.boot:spring-boot-starter-web")).

Let's create two small files to test our setup. Create a file called Application.kt in the src/main/kotlin folder and paste in the following:

Java
 
package com.example

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
open class Application

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}


This starts a default app using the Spring framework. The real magic happens in this next file, Controller.kt, which you should create alongside Application.kt in src/main/kotlin:

Java
 
package com.example

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
class GreetingController {

    @GetMapping("/{name}")
    fun get(@PathVariable name: String) = "Hello, $name"
}


Here, we define a route (@GetMapping("/{name}")), where {name} is a dynamic value. By placing this decorator over a Kotlin method (fun get, or "a function named get"), we're able to match the route to whatever behavior we want—in this case, returning a greeting with the path name for a GET request. 

In order for the IDE to know how to launch our application, we need to create a run configuration. At the top of the IDE menu, click the button that says Add Configuration…. Select Add new run configuration, then choose Gradle from the list. For the Gradle project name, enter kotlin-api. In the Tasks field, type bootRun. bootRun is a Gradle task provided by the Spring framework which will compile our code and start the server. Click Ok; you should now have a green Play button in your IDE menu bar. When you click on this, the IDE will execute  gradle bootRun to build this Kotlin app and start the server. When that finishes, navigate to http://localhost:8080/world. You should see a nice greeting.

Interacting with the database

Now, let's get to the (somewhat) serious stuff. Suppose we wanted to attach a database to this project. In a Maven/Java world, we'd need to update an XML file and add a reference to a JAR file. In Gradle, we can get by with just adding a few lines to our build.gradle.kts file:

Java
 
dependencies {
    # ...

    implementation("com.zaxxer:HikariCP:4.0.3")
    runtimeOnly("org.postgresql:postgresql")

    # ...
}


Here, we've included HikariCP in our project, which is a popular database connection driver. We also indicate that we want to "load" the org.postgresql library during runtime. With just these two lines, we've let our configuration know that we want to interact with a PostgreSQL database. If you already have a PostgreSQL database running locally, that's great. You'll be able to continue the rest of this guide locally and see the results when browsing localhost. If you don't have PostgreSQL, don't fret—we'll show you just how easy it is to deploy this app on Heroku, which will take care of the infrastructure for you. 

Head back to Controller.kt, and replace it with the contents below. This takes some of what we had from before but adds to it. We'll go over the changes shortly.

Java
 
package com.example
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.http.MediaType
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import java.net.URI
import javax.sql.DataSource

@RestController
class GreetingController {

   val dataSource = dataSource()
   val connection = dataSource.connection

   @GetMapping("/{name}")

   fun get(@PathVariable name: String) = "Hello, $name"

   @PostMapping(value = ["/add-name"], consumes = [MediaType.TEXT_PLAIN_VALUE])


   fun post(@RequestBody requestBody: String) : String {
       initDb()
       val stmt = connection.createStatement()
       stmt.executeUpdate("INSERT INTO names values('$requestBody')")
       return "Added $requestBody"
   }

   @GetMapping("/everyone")

   fun getAll() : String {
       initDb()
       val stmt = connection.createStatement()
       val rs = stmt.executeQuery("SELECT name FROM names")
       val output = ArrayList<String>()
       while (rs.next()) {
           output.add(rs.getString("name"))
       }
       val names = output.joinToString(", ")
       return "Here are the names: $names"
   }

   internal fun initDb() {
       val stmt = connection.createStatement()
       stmt.executeUpdate("CREATE TABLE IF NOT EXISTS names (name text)")
   }

   internal fun dataSource(): HikariDataSource {
       val config = HikariConfig()
       var dbUri = URI(System.getenv("DATABASE_URL") ?: "postgresql://localhost:5432/")
       var dbUserInfo =  dbUri.getUserInfo()
       var username: String?; var password: String?;
       if (dbUserInfo != null) {
           username = dbUserInfo.split(":").get(0)
           password = dbUserInfo.split(":").get(1)
       } else {
           username = System.getenv("DATABASE_USERNAME") ?: null
           password = System.getenv("DATABASE_PASSWORD") ?: null
       }
       if (username != null) {
           config.setUsername(username)
       }
       if (password != null) {
           config.setPassword(password)
       }
       val dbUrl = "jdbc:postgresql://${dbUri.getHost()}:${dbUri.getPort()}${dbUri.getPath()}"
       config.setJdbcUrl(dbUrl)
       return HikariDataSource(config)
   }
}


There's quite a lot going on here! Let's start from the bottom. We define a function called dataSource which provides a connection to our database. Because we're building a 12-Factor app, our database credentials are stored in an environment variable called DATABASE_URL. We fetch that URL and pull out the username and password from it if one exists. If not, we check another two environment variables for that information—DATABASE_USERNAME and DATABASE_PASSWORD. We then put all that information together into a format that the database connector needs. The initDb function creates a table called names, with a single text column called name. The /everyone endpoint has a @GetMapping decorator just like before. This defines a GET /everyone route, which gets all the names from the database.

Finally, we've added something rather new: a @PostMapping decorator. Here, we need to define what types of content this POST route can accept. In this case, it consumes a TEXT_PLAIN_VALUE media type (in other words, "Content-Type: text/plain"). Whatever string of information we put in the request body will be added to the database. In just a few lines, we've built a small API that we can add to and query.

If you start this server now—and if you have PostgreSQL running locally—you should be able to interact with it. Try making the following request:

$ curl -H "Content-Type: text/plain" -X POST http://localhost:8080/add-name -d 'Frank'

If you navigate to http://localhost:8080/everyone, you'll see that Frank was included.

Deploying to Heroku

It's time to see just how easy it is to get Kotlin running on Heroku. First, we need to create a file that's specific to Heroku: the Procfile. This text file defines how our application should boot and run. 

Create a file named Procfile in the root level directory, right next to your build.gradle.kts file. Copy-paste the following lines into it:

web: java -jar build/libs/kotlin-api.jar --server.port=$PORT

Here, we're saying that we want Heroku to run java -jar build/libs/kotlin-api.jar. That JAR is packaged and built during the deployment process; Heroku will create it automatically for us because it knows how to execute the Gradle task to do so. We are also binding the $PORT environment variable so that Heroku knows which port the server is listening to.

Next, run the following Git commands:

$ git init
$ git add .
$ git commit -m "Preparing my first Kotlin app"

Since we have the Heroku CLI installed, we can call heroku create on the command line to generate an app:

$ heroku create
Creating app... done, ⬢ desolate-plains-67007
Created http://desolate-plains-67007.herokuapp.com/ | git@heroku.com:desolate-plains-67007.git

Your app will be assigned a random name—in this example, it's desolate-plains-67007—as well as a publicly accessible URL.

In order to provision a database, we use the heroku addons command. Calling it without arguments will let us know if any exist:

$ heroku addons
No add-ons for app desolate-plains-67007.

No add-ons exist for our app, which makes sense—we just created it! To add a PostgreSQL database, we can use the addons:create command like this:

$ heroku addons:create heroku-postgresql:hobby-dev

Heroku offers several tiers of PostgreSQL databases. hobby-dev is the free tier, so we can play around with this without paying a dime.

Your code is ready, your Heroku app is configured—you’re ready to deploy. This is the easy part! Just type out the following command:

$ git push heroku master

Your code will be pushed to Heroku. From that point on, Heroku will take over. You'll see your build logs scrolling through your terminal. This will show you what Heroku is installing on your behalf and where you are in the build process. After it’s complete, you can visit your special URL in the browser (in this case, https://desolate-plains-67007.herokuapp.com) and interact with the API on the internet!

Learning more

Kotlin's performant design and legible syntax—combined with the ease of Gradle—make it ideal for enterprises that need to rely on battle-tested Java packages. Because of its interoperability with Java, Kotlin is ideal as a transitional language; vast swaths of Java code can be converted into Kotlin while still maintaining a functional app. Deploying to Heroku is smooth, and I didn't even take advantage of the different ways to fine-tune Java and JVM-based apps for deployment.

app Kotlin (programming language) Database connection Java (programming language) Git Spring Framework operating system Gradle

Opinions expressed by DZone contributors are their own.

Related

  • Realistic Test Data Generation for Java Apps
  • Spring Microservice Tip: Abstracting the Database Hostname With Environment Variable
  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!