After Java, Kotlin is the most popular JVM language. Kotlin is an expressive, concise language with strong functional programming support. It’s especially appealing to Java developers because it is fully interoperable with Java, and the syntax is an easy transition. Let’s take a look at this dynamic programming language.
Get started with Kotlin
Kotlin is a great language for Java developers to expand into. It complements and embellishes what you already can do with Java and offers serious power from within the JVM ecosystem. Best of all, learning and using Kotlin doesn’t demand much from your already overworked brain, as switching between Java and Kotlin is fairly simple.
Like Java, Kotlin requires that you have a JDK installed. The command-line tool SDKMan makes installing and managing Kotlin simple:
$ sdk install kotlin 2.0.20
$ kotlin -version
Kotlin version 2.0.20-release-327 (JRE 23-ea+24-1995)
Once installed, you can create and run a simple Main.kt
file:
// Main.kt
fun main() {
println("Hello, InfoWorld!")
}
To compile it, enter:
$ kotlinc Main.kt
This command outputs a class file: MainKt.class
, which you can run just like any other:
$ java MainKt
Hello, Kotlin!
Notice that a function with no return value, like the one above, doesn’t declare a void return value like in Java. Instead, it simply has no return modifier at all. Unlike Java, you can declare a function with the fun
keyword outside of a class. In simple cases, functions lack all the trappings we’d find in Java: no package, class name, or public static void qualifiers. Kotlin has all these capabilities, but it hides them by default, using conventions to provide a simpler syntax up front.
The basic idea in this section was to show how easy it is to write your code in Kotlin, which is like a streamlined and expanded Java, and run it within the JVM. It’s not that common to run Kotlin directly with Java tooling, but the example makes clear the relationship between Kotlin and Java. Usually, we’d run Kotlin using the kotlin
runtime or a build tool like Gradle, which you’ll see in a moment.
First-class functions in Kotlin
Kotlin is a first-class functional language. It lets you pass around and return functional references from other functions, which provides enormous flexibility.
It is also often valid to write Java code right beside Kotlin:
// Main.kt
fun main() {
System.out.println("Hello from Java, InfoWorld!");
println("Hello, InfoWorld!")
}
On the other hand, some differences in Kotlin’s syntax make standard Java invalid. For one thing, Kotlin does not have or allow primitive types. In this respect, it is even more Java-like than Java: everything in Kotlin really is an object. There are no int
, long
, double
, or char
exceptions. (Project Valhalla is moving Java in a similar direction.)
Kotlin also makes a strong distinction between mutable and immutable variables. This is a common trend in modern languages. Any time you can live with an immutable version, you reduce the complexity of the program. In Kotlin, val
means an immutable variable, while var
means mutable:
val myValInt: Int = 10;
var myVarInt: Int = 10
// myValInt++;
You may have also noticed by now that semicolons are optional in Kotlin, as they are in JavaScript. However, the common practice in Kotlin is to avoid using terminating semicolons.
Kotlin infers types like so:
val myString = "FooBar";
println("My string ${myString} is a classic.");
The above snippet also demonstrates Kotlin’s built-in String
interpolation support, which has a similar syntax to many templating tools. The dollar-sign curly-brace (${}
) can also contain expressions, as in: ${myString.upperCase()}
. If the variable name is one without special characters, you can use a simplified form of it:
println("When in doubt, $myString.");
Beneath everything is Java’s type system, which you can access by typing:
println(myString::class.java.typeName); // Outputs “String”
Nulls
One of Kotlin’s embellishments is its more explicit handling of nulls. The NullPointerException
is one of the most familiar exceptions in Java. In Kotlin, variables are non-nullable by default. The compiler will not allow you to set a null
on normal variables. If you want a nullable version, you can set it up like so:
val myNullableString: String? = null
Kotlin also has the .?
and :?
operators to help deal with nulls. The .?
is similar to JavaScript’s recently added optional chaining operator, and lets you short-circuit on null values without verbose checks:
possiblyNull.?possiblyNullMember.?anotherMember
If any of the parts are null, the whole expression returns null, without error. The nullish coalescing operator (?:
—also called Elvis operator because it’s like an emoji with Elvis hair) lets you test the left side for null and return it if its non-null. Otherwise, it’ll return the right side, which may also be null:
something :? somethingElse
If something
is null, you’ll get somethingElse
.
Collections
Of course, you need to be able to handle collections of variables, and Kotlin has all the sets, lists, and maps you’d expect. These also allow for both mutable and immutable variants. So if you want a mutable list of strings you can enter something like:
import kotlin.collections.*;
fun main() {
val books: MutableList = mutableListOf("Autobiography of a Yogi", "Slaughterhouse Five", "Phaedrus");
println(books[2]);
}
Notice we imported the collections library in this snippet. Since that library exists in the Kotlin standard library, we can compile it:
$ kotlinc Main.kt
But it won’t run. Instead, we get
$ java MainKt
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/collections/CollectionsKt
at MainKt.main(Main.kt:14)
at MainKt.main(Main.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.collections.CollectionsKt
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528)
This is a familiar Java error telling us a class definition for CollectionsKt
can’t be found. To get around the error, we need to include the standard library during runtime, like so:
$ java -cp /home/matthewcarltyson/kotlin:/home/matthewcarltyson/.sdkman/candidates/kotlin/current/lib/kotlin-stdlib.jar MainKt
Phaedrus
This command tells Java to include the kotlin-stdlib.jar
(though yours might be be in a different location). Another approach is to use the kotlin
runtime, which automatically includes stdlib
for you:
$ kotlin MainKt
Kotlin also gives you strong functional programming support. For example, to check our books
collection for a book we want, we’d enter:
println("Phaedrus" in books) // outputs true
Also note that we can access lists just like arrays, using brackets:
println(books[2]) // outputs “Phaedrus”
Using Gradle with Kotlin
Gradle is often used as a build tool in Kotlin because you can use the Kotlin DSL instead of Groovy.
The easiest way to use Gradle and Kotlin together is to start a fresh project with Gradle init. That command launches an interactive questionnaire from the command line, shown with my answers here:
$ gradle init
Select type of project to generate: 2: application
Select implementation language: 4: Kotlin
Split functionality across multiple subprojects?: 1: no - only one application project
Select build script DSL: 2: Kotlin
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]: no
Project name (default: kotlin): kotlin
Source package (default: kotlin): com.infoworld
My selections results in a Kotlin application using Kotlin as the build script language. Now, you can run this simple program from the project root with:
$ ./gradlew run
When you run the program, you’ll get a “Hello, World” greeting. Here’s how the project is laid out:
/settings.gradle.kts - Global settings for Gradle
/app/ build.gradle.kts - The build file
/app/src/main/kotlin/com/infoworld/App.kt - The single source file
Looking at the App.kt
file, you’ll see:
package com.infoworld
class App {
val greeting: String
get() {
return "Hello World!"
}
}
fun main() {
println(App().greeting)
}
There are some features here that you have not yet seen, including the use of a package declaration (in Kotlin, the package and directory structure don’t strictly have to match).
You can also see Kotlin’s streamlined syntax for declaring a class. In Kotlin, thedefault visibility of a class is public, so App
is a public class. Inside of it, there is a read-only String
member called greeting
(remember that val
declares a read-only variable). Notice that the greeting property declares a get()
function. This is what will be executed when we use the dot operator to access it, something like a streamlined getter.
Now let’s attempt something more ambitious and download the character data for Chewbacca from the Star Wars API. We can modify the App.kt
file like so:
//app/src/main/kotlin/com/infoworld/App.kt
package com.infoworld
import com.google.gson.Gson
import java.net.URL
class App {
fun fetchAndPrintCharacterInfo(url: String) {
val gson = Gson()
val response = URL(url).readText()
val character = gson.fromJson(response, StarWarsCharacter::class.java)
println("Name: ${character.name}")
println("Height: ${character.height}")
}
}
data class StarWarsCharacter(
val name: String,
val height: String,
)
fun main() {
val chewbaccaUrl = "https://swapi.dev/api/people/13/"
val app = App()
app.fetchAndPrintCharacterInfo(chewbaccaUrl)
}
This gives us a look at Kotlin’s data class, which is designed for holding information (something akin to Java’s value objects). The StarWarsCharacter
class has all the standard methods like getters and setters, hashCode
, toString
, and equals
. It is ideal for unpacking API data into a container, which is what we’re doing here.
Add the following dependency to the dependencies
section of /app/build.gradle.kts
:
implementation("com.google.code.gson:gson:2.9.1")
This lets us handle the JSON we’ll get back from the API. Now, if we run the app we’ll see some information about Chewbacca:
$ ./gradlew run
> Task :app:run
Name: Chewbacca
Height: 228
Hair color: null
Eye color: null
BUILD SUCCESSFUL in 2s
Conclusion
The beauty of Kotlin for Java developers is that it fits fairly easily into your existing mental model. Kotlin lets you program inside the JVM, with all its intensive optimization and vast ecosystem, but with a functional language that is in some ways “more Java than Java,” yet easy to grasp and powerful. You can also use Kotlin alongside Java, so you don’t have to choose one or the other.
There’s a lot more to Kotlin, including an extension function (which lets you add class functions without subclasses), coroutines, more functional capabilities, lack of checked exceptions, and a simplified approach to object-oriented programming. We’ll continue exploring Kotlin and its features in my next article.