A very brief overview of Kotlin for Java developers

Java logo Kotlin is a Java-compatible programming language that has increased in popularity very quickly over the last few years. The popularity has been enhanced -- to some extent -- by the adoption of Kotlin by Google for Android app development. The language does have some features that make Kotlin very suitable for this application.

In this article, I provide the briefest of brief overviews of the Kotlin language, for experienced Java developers. At the end I will try -- and largely fail -- to explain why Kotlin does not have feature parity with Java.

This article is based on version 1.5.31 of the standard Kotlin compiler.

Compilation and code generation

The Kotlin compiler is written in Java, and outputs Java bytecode (although a version that outputs native code is now available). The bytecode is written into .class files with the same format as those generated by javac. By default, the Kotlin compiler produces bytecode that is compatible with Java 1.8. Although this default can be overridden, I don't think that other language features become available by selecting a later bytecode target.

Kotlin code has a type model that is quite similar to Java's and, to a large extent, direct calls can be made from Kotlin code to Java code. The Kotlin compiler tool kotlinc can both compile and run Kotlin code -- in both cases the work is actually done by a Java JVM. kotlinc has many of the same command-line switches as javac: -classpath, -Werror, etc.

kotlinc can output to a self-contained ("fat") JAR file, or to a directory structure, by specifying the -d switch, as for javac. As in Java development, the generated class file structure will mirror the application's package structure.

The Java byte code generated by kotlinc can be executed with a Java JVM -- there is no specific Kotlin runtime. However, there is a standard Kotlin library that might need to be included with the compiled bytecode, and this will not be done automatically. You'll need to use the -include-runtime switch. Many developers come to the false impression that -include-runtime means to include a virtual machine, that is, to produce code that is independent of Kotlin or Java. This is not true -- "runtime" is a misnomer: it means only to include supporting classes in the generated output. Unless you're using the native-code compiler, you'll always need a Java JVM to run Kotlin code, and you might need to bundle the Kotlin "runtime" (classes) as well.

Package hierarchy

Kotlin's semantic packaging is essentially the same as Java's -- both in language syntax and in the structure of the generated bytecode. In both languages, a source file typically starts with

package foo.bar;
although the different syntax of Kotlin means that the semicolon is not necessary. The import keyword has exactly the same function in Kotlin as it does in Java.

Kotlin files have mandatory naming rules, as is the case with Java (but not C or C++). Source files are expected to end with .kt. Generated bytecode files will always have names ending in .class.

Kotlin's basic syntax

Kotlin's language syntax is similar to that of Java and C++, but not that similar. Superficially it looks a little more like Pascal, but it's block-structure rules are exactly like those of Java and C++, and not at all like Pascal's. As in Java/C++, language blocks are indicated using curly brackets -- {} -- and these are used consistently and uniformly. The Kotlin syntax means that statements do not normally need to be separated using semicolons.

However, a consequence of using a syntax that does not rely on delimiters is that, unfortunately, end-of-line markers have syntactic significance. There are places in which an opening curly bracket cannot be the first non-whitespace token on a line. This is a nuisance for developers (like me) who prefer to put block-marking tokens like curly brackets on lines of their own. Sometimes this works in Kotlin, and sometimes it doesn't. Personally, I don't like compilers to dictate how I lay out my code; but I guess Python programmers learn to live with it. In any event, Kotlin has less layout flexibility than Java.

Kotlin supports the same flow control and loop constructs as Java, although with a somewhat different syntax.

There isn't really space here to describe the language syntax in detail -- see the Kotlin manual for details.

Kotlin types

Kotlin has the same primitive data types as Java, except that it has (yipee!!) unsigned types like UByte and UShort.

The Kotlin language does not distinguish between true primitive data types (like Java's int) and class-based data types (like Integer). At the language level, Kotlin's primitives are all objects.

Using object primitives avoids some of the type-conversion nastiness that we find in Java -- the kind that was supposed to be avoided by "autoboxing". The autoboxing still happens, but it's largely invisible. A Kotlin Int will be represented as a Java int where possible, and an Integer object where it isn't.

There's a little more strictness in assignment of short types in Kotlin, compared to Java. For example, this is legal Java:

char c = 'a';
But the corresponding Kotlin code:

val c: Char = 'a'
fails. Although this kind of code is widespread, it is right that the compiler should complain about it, because there is no guarantee that c + 1 will fit into an object of Char range. Kotlin expects the result of an operation on Char or Short to be assigned to Short or Int respectively. No doubt this will cause annoyance, but from a code quality point of view, it's the right thing to do.

I guess this is unavoidable, but a Kotlin Char has 16-bit signed range, like a Java char. This makes a Kotlin Char unsuitable in general for storing a Unicode code point, whose value might not fit into 16 bits. There's a lot of bad Java code floating about that was written without understanding this fact, which the Java platform specification does nothing to avoid. Kotlin could have taken a stand and fixed this problem but, probably, only by limiting Java compatibility.

Kotlin has notional support for ranged integer types, that is, integers that are constrained to take certain values only. However, at the time of writing, this feature is essentially undocumented, and it's not clear to me how effectively it is implemented.

Primitives and objects can be collected into arrays, whose semantics are exactly as for Java, although the language syntax is a little different.

The Kotlin equivalent of Java's void is Unit.


Kotlin's use of variables is much the same as Java's, but there's enough syntactic difference to cause confusion. Like Java, Kotlin is statically typed, meaning that the type of a variable will not change after it is defined. Like modern (11+) versions of Java, Kotlin also supports type inference -- you don't need to state the type in a definition if it is clear to the compiler.

Definitions are Pascal-like:

var answer:Int = 42
var answer2 = 42 // Also OK
var answer3:Double = 42 // Fails, because 42 is an integer
Unlike Java, Kotlin will warn if a variable is defined but never used

Read-only variables (constants, essentially) are introduced using the val keyword, while var denotes an ordinary (assignable) variable. The const keyword in Kotlin functions rather like final in Java, denoting a variable whose value is fixed at compile time.

Kotlin's class model

Kotlin's class model is essentially the same as Java's, as are the language keywords associated with it. Essentially the same member visibility and scope rules apply, with the notable exception that there is no support for static member functions (more on this later). As in Java, Kotlin classes support only single inheritance, but can simulate multiple inheritance through the use of interfaces. Of course, this is not the only use of interfaces. As in Java, Kotlin classes can be abstract.

Kotlin classes have a notion of primary constructor. This is a specialized constructor that can also define member variables. This use of primary constructors fits nicely with the idea of "data classes".

A data class is a bit of syntactic candy to make a common class use-case more expressive. Consider this implementation:

data class Complex (val real: Double, val imag: Double) { }
Defining Complex as a data class has the effect that the arguments to the primary constructor automatically become member variables. Thus a caller can refer to the object's .real and .imag members without additional definition. The constructor does not need to be implemented by the programmer: the member variables are assigned automatically from the constructor arguments when the class is instantiated.

Defining a data class also provides additional features. For example, the compiler will implement boilerplate hashCode() and equals() methods that may, or may not, be useful in a particular application.

The syntax for instantiating a class in Kotlin is somewhat different to that of Java: there is no new keyword. Instead, a new instance is obtained by calling the constructor as if it were a method.

One construct that Kotlin provides, and that Java does not, is the sealed class. A sealed class has fixed subclasses, all known at compile time, and defined in the same source file. This construct provides a handy and efficient way to use subclasses as choices, like a more flexible version of an enum (which Kotlin also supports).

Another feature exclusive to Kotlin is extension classes. Unlike Java, Kotlin allows methods to be added to classes from outside the class definition. A potentially useful application of this feature is to extend classes in libraries, for which source code is not available, or where you simply don't want to modify the library (because it's a managed dependency). Developer surveys have showed that many Kotlin developers rate extension classes as the language's most significant feature, and a strong reason for adoption.

Kotlin has no static keyword, so does not support Java-style static member functions. If you want to define a method that has class, rather than instance, scope, you can define a companion object. This is essentially a singleton instance of the class, for which Kotlin provides syntactic support. The use of singleton classes is, of course, widespread in Java; the difference is that these are first-class language constructs in Kotlin.

In short, although Kotlin's class model is essentially the same as Java's, the syntax will be more compact and readable for many common use-cases.

Function call mechanism

Kotlin's function call mechanism is identical to Java's. In particular, when an object is passed as an argument to a function, the function can change the contents of the object, if the object allows that. There is no notion of "const correctness" in Kotlin, just as there is not in Java. That is, there's no way to specify that a particular method will not change the contents of an object passed to it, and no way to specify that a particular method does not change the contents of the object of which it is a member.

Kotlin, like Java, lacks a way to pass primitive data elements by reference.

The lack of const-correctness and an ability to pass primitives by reference are irksome to C++ developers; but Java developers have, presumably, learned to live within these limitations, and won't be bothered by the absence of these features from Kotlin.

Kotlin's non-class functions

Kotlin does not require that functions be defined only as member functions of classes, as Java does. It's perfectly permissible to create a Kotlin source file that contains only function definitions.

This makes Kotlin more immediately "teachable" than Java, because "Hello, World" is simply this:

fun main()
  println("Hello, World!")
Running this simple program at the command line amounts to:
$ kotlinc test.kt
$ java TestKt 
Hello, World!
Note that TestKT is the automatically-generated class name provided by the Kotlin compiler, since the main method is not in a class.

Note also that the main function does not need to take any formal parameters, if the program does not actually process command-line arguments, nor does it need to define a return value.

To an absolute beginner, this all looks much simpler than Java -- no boilerplate class, no static void main... For better or worse, though, Kotlin does not allow a minimum program of the form:

println("Hello, World!")
As both Perl and Python do. I taught introductory programming for years, to all kinds of students, and I remain unconvinced that the simplicity of the very earliest programming example has a large impact on student success; but I could be wrong. In any event, Kotlin is a more teachable language in the earlier stages than Java, but less so than Python, probably not to a huge extent.

What happens to non-class functions, in practice, is that they are turned into static, public member functions of a class whose name is based on the source filename. When a non-class function is called, the Kotlin compiler turns the function call into a method call on this public class.

Of course, to do this the compiler will have to locate the function name in the source files that are available to it. If the same function name exists in more than one file, then the call is rejected as a "conflicting overload".

It's possible to call from Kotlin code a static method in any Java class, by denoting the classname, and such a call has exactly the same syntax as it does in Java. However, Kotlin itself does not really have static methods as part of its language syntax, so it isn't possible (in an elegant way) to call a Kotlin non-class method by referencing its automatically-generated class name.

To some extent, the abilty of Kotlin to define functions without classes supports the built-in Kotlin functions like println. These methods are not implemented by some complicated search of the objects available in the Java runtime, although it might be nice if there were such a mechanism. Instead, these built-in in functions are implemented in the Kotlin standard library as inline functions:

public actual inline fun println(message: Any?) {
The inline modifier here denotes a straightforward replacement of the call to println with a call to System.out.println which, as I said, is a legitimate call in Kotlin.

Inline functions

Like C, and unlike Java, Kotlin allows user functions to be declared as inline -- this feature doesn't apply only to internal functions. An inline function is substituted directly into the code of the caller, removing the overhead of a function call.

In a language like C that is traditionally compiled completely to machine code ahead of execution, inlining functions can have significant benefits in skilled hands. In Java, strong arguments were raised against providing a comparable feature, because inlining was held to be the job of the just-in-time compiler. No version of the standard Java compiler supports inline function declaration, and it's not clear to me that it offers much benefit in Kotlin, except perhaps as a way of reducing code complexity a little. The Kotlin compiler does have the decency to warn if you're using inline in a way that is unlikely to improve throughput.


Unsurprisingly, Kotlin handles exceptions much like Java. However, Kotlin does not support "checked" exceptions. It is never necessary to specify that a method catch or propagate a particular exception; if the exception is not caught and handled somewhere, eventually the JVM will shut down. Kotlin exceptions thus all function like "runtime" exceptions in Java.

The lack of checked exceptions seems to me a disadvantage from a code quality point of view, although I'm aware of arguments in favour of avoiding them. These arguments all seem to center on the fact that developers tend to use checked exceptions in a careless and half-hearted way. That's no doubt true, but it seems a bad reason to throw away a potentially powerful feature. In any case, most C++ compilers don't support checked exceptions either -- Java seems to stand alone here.

Operator overloading

Unlike Java, Kotlin supports full programmer-defined operator overloading. It also supports the same lazy, ill-considered default overloading of Java. For example, it's still possible to concatenate a string and the string form of a number using the "+" operator, in defiance of all logic.

Still, programmer-defined operator overloading is a big deal in some kinds of programming, and its absence from Java is regrettable. For example, in mathematical work it would be useful to express calculations on complex numbers with ordinary math symbols: "+", "*", etc. For example, it would be nice to be able to write:

  val a = Complex (1.0, 2.0)
  val b = Complex (3.0, 4.0)
  val c: Complex = a + b
Happily, in Kotlin, we can. We define the Complex class like this:
data class Complex (val real: Double, val imag: Double)
  operator fun plus (other: Complex): Complex
    return Complex (real + other.real, imag + other.imag)
  // Other complex number functions...
operator fun defines a function that implements an operator, "plus" in this case.

This is a feature that provides enough rope for us to hang ourselves. There's nothing to stop us overloading operators in ways that make no sense -- using minus to represent an addition, for example. Of course, it's always possible to write unhelpful code if we are really determined to do so.

Most of the built-in operators can be overloaded. For better or worse, Kotlin's overloading is not as flexible as that in C++. For example, we can't define a new kind of an array, and provide our own implementation of the [] index -- C++ does allow this.

Incidentally, the C++ standard template library overloads the bit shift (<<) operator to represent data transfer. This was a terrible idea -- overloading an operator with a new function completely unrelated to the old one -- but one that is quite visually expressive. Kotlin does not allow this but, because it doesn't use punctuation for shift operations, it would be pointless to do so.

Kotlin lambdas and functional programming

Much is made of the fact that Kotlin accepts blocks of program code as first-class syntactic structures. That is, a piece of code -- perhaps one that takes arguments and returns a value -- can be assigned to a variable, or passed to another function. Conventionally, a function that takes another function (not the result of a function call) as an argument is called a higher order function. This all has the potential to be very useful but, in fact, recent versions of Java have similar features. It isn't clear to me how much Kotlin has the lead in this area.

In any event, here is a canonical example of defining a higher-order function in Kotlin. This function takes an array, and a function that defines a transformation to apply to each array element.

fun applyToArray (array: Array<Double>, operation: (Double) -> Double)
  for (i in array.indices)
    array[i] = operation (array[i]);

The function applyToArray takes two arguments. The first is simply the array -- in this case it is an array of Double values to which a transformation is to be applied. The second argument is a function called operation. We don't know (at this point) what the function does, but the declaration says that it takes an argument of Double type, and returns a Double result. Thus applyToArray can apply to every member of the array any single-argument function. Note that this function changes the array -- this is important because an alternative approach might be to return a new array containing the modified values. Either approach is valid, and either might be useful in particular circumstances.

Here's how we might call operation to square every member of an array.

 val a = arrayOf<Double> (1.0, 2.0, 3.0)
 // Square all the elements
 applyToArray (a, { elem: Double -> elem * elem })
Here we're calling applyToArray and passing this function: { elem: Double -> elem * elem }. This is an anonymous function -- it has all that a function should, apart from a name. We don't have to use this formulation; sometimes a named function is more readable:

fun square (x: Double): Double { return x * x; }
// ...
applyToArray (a, ::square)
It's crucial to understand that we aren't passing to the applyToArray function the result of evaluating some other function -- we are passing the function itself. Or, at least, we're passing some kind of reference to it, that allows it to be called repeatedly.

Because this kind of thing is so useful, you probably won't be surprised to learn that similar higher order functions are already defined for arrays in the Kotlin language. So we could square every element in array a like this:

a.forEachIndexed { i, elem: Double -> a[i] = elem * elem }
There are other "functional programming" techniques available in Kotlin, and increasingly in Java. There's no question that these are powerful techniques, that have the potential to make code much more compact. My own feeling, though, is that these techniques are often overused, with the result that code becomes difficult to read and maintain. There aren't -- in my view -- all that many cases where it makes sense to use functional programming structures in what is, after all, a procedural language. However, there are a few, well-known instances where these techniques can lead to a huge improvement in both the readability and compactness of code.

A good example is in applying actions to user interface gestures. It's not uncommon for a user interface to have hundreds of elements, each of which the user can interact with in many different ways. Each of these interactions leads to a specific action, which must be specified in code.

The boilerplate way to specify the behaviour of a user interface in Java Swing (or Android, at least until recently) was to apply a listener to each user interface element; each listener would take the form of an anonymous inner class, of which certain methods would be overridden. Every time I see code like this -- line after line of anonymous class boilerplate -- my heart sinks: it's incomprehensible, and impossible to maintain.

A more modern approach, using lambda functions, might have constructs like this:

exitButton.setClickAction (::exitProgram);
Until you've worked extensively with the alternative, it's hard to appreciate just how radical an improvement this is -- at least for Java. We've been able to do the same thing in C using function pointers for forty years. What we can't easily do in C, but can readily do in Kotlin, is to specify user interface actions that take parameters and return results.

That Kotlin makes it easy to code user interface actions is perhaps one of the reasons it has been so rapidly adopted for Android -- but it probably isn't the only reason: see the next section.

Coroutines and threads

Kotlin supports the same thread model as Java: you can create a subclass of Thread, or implement the Runnable interface. Such constructs generally create independent operating system threads, just as they do in Java -- with all the problems of locking and synchronization that entails.

Kotlin also supports coroutines as a first-class language feature. Coroutines offer a kind of cooperative multithreading, and feature strongly in languages like Lua and Python, that don't have "true" multithreading. Since Kotlin does support true multithreading -- because the JVM does -- it's perhaps a little odd that coroutines feature so strongly. However, there are situations in which coroutines are preferable to threads.

I suspect that many Java developers will be unfamiliar with coroutines, so here is a (Kotlin) example.

fun main()
  runBlocking { 
     launch {
        while (true) {
          delay (100L)
      launch {
        while (true) {
          delay (100L)
Running this code will result in "CR1" and "CR2" being written to the console, in strict order, indefinitely. There are two while loops, that run as separate coroutines, each of which just delays and then prints its text in an endless loop.

This formulation certainly appears to be creating two independent threads, one in each launch block. It works because the delay() method is not simply a time delay -- it's an opportunity to wake up other coroutines that are also blocking (in a delay, or in some other way). The method Thread.sleep() is simply a delay, and any task switching that happened during the delay period would be the result of thread scheduling in the operating system. The two coroutines, however, run in the same thread -- this is something you can prove by running the program and using jstack to dump a list of running threads.

This is "cooperative multithreading" because coroutines have to be aware that they are running as coroutines, and take the necessary steps to ensure that other coroutines get a share of the CPU. If we remove the delay call in one of the coroutines above, that coroutine wouldn't just get a bigger share of the CPU, it would get all the CPU, because there would be nothing to switch execution to a different coroutine.

Coroutines are potentially very useful in implementing event-driven user interfaces. Typically the user interface of a particular application will be managed on a single thread, so actions initiated by the user interface must complete quickly. If this doesn't happen, then the whole user interface stalls. If the user starts an action that takes a significant time -- creating a network connection, for example -- then the whole program will appear unresponsive. Unless specific provision is made, it won't even be possible to cancel the long-running action, because the user interface won't respond to the user.

This problem can be solved, in principle, by putting long-running actions into separate threads, and allowing the user interface to continue running. Unfortunately, doing this raises the problem of communicating between threads, and ensuring that the threads synchronize properly.

Coroutines potentially offer a better solution because they are not, in fact, threads, even though they allow different parts of a program to execute in a way that appears concurrent. The flow of execution between coroutines is deterministic and predictable, in a way that is not the case for true threads. This means that there are fewer opportunities for race conditions, and none at all for thread deadlocks.

Because coroutines are, by their very nature, cooperative, a user interface based on coroutines must be co-routine aware. However, so must any other action that might block. In the example above, if we replace the delay() call with a Thread.sleep(), the entire application fails -- the sleep() method is not coroutine-aware.

The Android user interface model is one that is well-suited to programming using coroutines. This, again, is perhaps a reason why Kotlin has become so closely associated with Android.

Finally, it's worth bearing in mind that coroutines can be implemented in Java. This must be true, because Kotlin code is running on a JVM. However, at present coroutines are not a first-class feature of Java, and most features in the standard Java runtime library will not integrate well with a coroutine implementation.

Null safety

Kotlin takes a much stronger line on the handling of null values than Java does. It's very difficult to see the infamous "null pointer exception" in Kotlin applications, because situations that might lead to it can be trapped at compile time.

It's perfectly legitimate to assign null to a variable or function argument in Kotlin, but the code must specifically denote where this is allowed. The question mark (?) symbol indicates that a variable or argument may be null. This snippet of code will not compile:

fun foo (x: Int)
  //... Do something on x

fun main()
  var x: Int? = null
  foo (x)

The value of x in main is allowed to be null, but the argument to foo() is not. The compiler will report that you can't assign an Int to an Int? -- they are, in a sense, different types of variable.

This method of handling null values is not foolproof, because Kotlin can call Java code, which does not have the same checks. Still, it's a welcome step forward in terms of improving static code quality.

Maven compatibility

Maven is a build tool and dependency manager that is widely used by Java developers. Recent versions of Maven are compatible with Kotlin, and the use of Kotlin and Java in the same project is well documented.


Because Kotlin applications run on a Java JVM, all the Java debugging methods we have come to know and (?)love work identically with Kotlin. We can collect thread and heap dumps, look at GC logs, use a Java debugger to insert breakpoints, etc.

In practice, however, there is not the same degree of correspondence between source code and bytecode for Kotlin as there is for Java. Some Kotlin features that are easy to express as source code result in the generation of very complicated bytecode, supported by very complicated libraries. A case in point is coroutines -- there's really no clear mapping between the source code we want to debug, and its implementation as bytecode. Looking at a thread dump from an application that uses coroutines is not at all revealing.

Some IDE tools have debuggers specifically for Kotlin, and I imagine that this trend will continue.


As a long-time Java developer, I find Kotlin a strange creature. I suspect that most experienced Java programmers could become proficient with Kotlin in a few hours -- there's almost a one-to-one mapping between Java features and Kotlin features. Some Java developers will struggle with coroutines and, I suspect, many have not yet fully embraced lamdas and functional programming. But that's not the fault of Kotlin -- these are well-established programming paradigms, and there's nothing particularly awkward about their Kotlin implementations.

And yet...

Does Kotlin offer any improvement over Java? Its proponents talk about improved developer productivity, and it's certainly true that many common design patterns can be expressed quickly and concisely with Kotlin. Kotlin does seem to require less boilerplate code than Java, but I suspect most Java developers use IDE tooling to generate all the boilerplate.

Kotlin seems to me a language that was developed in a highly opinionated way, and with very specific applications in mind. I find it almost impossible to account for some of the design decisions.

For example, Kotlin has certain features that suggest that static code quality is a priority -- strict assignment rules for integers of different size, warnings about unused variables, null safety. At the same time, the language sheds checked exceptions, and does not take the opportunity to implement const correctness -- which requires exactly the same kind of static checking as null safety does.

Kotlin implements coroutines and traditional threads as first-class language features -- I suspect it is the only mainstream programming language that does this. I'm sure there's a good reason for this, and it's called Android.

It almost looks as if Kotlin has been designed specifically to develop Android apps. The static code quality features are particularly important here, because run-time debugging on Android is horrific. Android needs concise, expressive ways to code user interface handlers, and the functional programming elements of Kotlin work well here. There's no need to provide checked exceptions because, if an exception is not trapped, it will just shut the app down -- and that's something that Android apps have to be designed to tolerate anyway.

Nevertheless, I find the inclusion of operator overloading a little strange. This is a feature I welcome, and whose absence from Java I lament; and yet I doubt that it will be of much interest to many developers. The maintainers of the Java language have steadfastly refused to include this feature. In fact, it's one of the few C++ features that was deliberately excluded from Java, that has not crept back in over time. Similarly, I don't fully understand why the designers of Kotlin would decide to include unsigned integer types, while the Java designers decided not to. Either decision is comprehensible; it's just not clear why Java would go one way and Kotlin the other. The reason for including extension classes is also not very clear to me, although this has proven to be a popular feature.

I suspect that, like Perl, Kotlin -- and Java, to some extent -- is a language of expediency. It seems to have a set of features that make it easy and safe to code constructs that are particular important for Android development.

Still, there are uses for Kotlin outside Android. Apache Camel -- an integration framework for middleware applications -- now has native support for Kotlin, as does the Wildfly application server. Clearly there are reasons to prefer it over Java, but whether those reasons are technical or expedient, I really couldn't say.