visit
// 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.
// 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-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.
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:
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:
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:
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 . At the top of the IDE menu, click the button that says Add Configuration. Then, select Add new run configuration. Next, 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 //localhost:8080/world
. You should see a nice greeting.
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:
dependencies {
# ...
implementation("com.zaxxer:HikariCP:4.0.3")
runtimeOnly("org.postgresql:postgresql")
# ...
}
Here, we've included 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.
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, 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.
$ curl -H "Content-Type: text/plain" -X POST //localhost:8080/add-name -d 'Frank'
If you navigate to //localhost:8080/everyone
, you'll see that Frank
was included.
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.
$ git init
$ git add .
$ git commit -m "Preparing my first Kotlin app"
$ heroku create
Creating app... done, ⬢ desolate-plains-67007
Created //desolate-plains-67007.herokuapp.com/ | [email protected]: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.
$ 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, //desolate-plains-67007.herokuapp.com
) and interact with the API on the internet!