
The Java virtual machine provides a high-performance, universal runtime for a wealth of popular languages beyond just Java. In this article, we’ll look at the characteristic strengths and common use cases of four of the most popular JVM languages: Kotlin, Scala, Groovy, and Clojure.
Kotlin
Kotlin is a modern language that has seen a groundswell of developer enthusiasm over the last few years. This popularity is thanks in large part to its highly expressive syntax, which includes object-oriented and functional programming support, but it doesn’t stop there. Kotlin is interoperable with Java, and it includes multiplatform tooling and cross-language compilation. Like other JVM languages, you can use GraalVM to compile Kotlin to native binaries for highly optimized deployment with excellent start, stop, and runtime resource use.
In 2019, Google identified Kotlin as the preferred language for Android development, a vote of confidence that turbo-boosted its popularity with developers.
Another factor in Kotlin’s strength is its backing by JetBrains, the creator of the IntelliJ IDE. JetBrains has consistently maintained and refined Kotlin. That investment has ensured Kotlin’s stability while keeping it on the leading edge of innovation, both qualities developers appreciate.
Because it is 100% interoperable with Java, Java developers and organizations can adopt Kotlin gradually. It is easy for a Java developer to get comfortable with Kotlin, and vice versa. It is also not hard to hold both languages in your head. For experienced Java developers, Kotlin feels like an expanded version of Java. And even if you don’t know Java, you can still become an expert in Kotlin.
Kotlin obviously shines for use on Android, but it’s also popular in other areas, including server-side development. Kotlin is well-suited to developing DSLs (domain-specific languages). One of these, the Kotlin HTML DSL, is a powerful, built-in server-side templating language for the web.
One of Kotlin’s best-known assets is its null safety feature, which enables minimizing the occurrence of NullPointerExceptions. Standard types like String cannot be initialized null, unless you explicitly allow it using the nullable modifier (String?). When using nullable types, the compiler disallows access without a safety check. Kotlin also gives you the null-safe dot operator (?.), which is similar to the optional chain operator in JavaScript. Here’s a look at Kotlin using the ?: operator to provide a default value when checking:
val length = middleName?.length ?: 0
In this example, if middleName is null, length will be set to 0.
Another killer feature is coroutines, which provides a structured way to manage concurrent operations. Kotlin’s coroutines are inspired by Go’s goroutines, and also were an inspiration for Java’s new structured concurrency model. This example shows how a Kotlin coroutine can be used to provide synchronous syntax for asynchronous logic:
import kotlinx.coroutines.*
fun main() = runBlocking { // main coroutine
// Launch a new coroutine
launch {
delay(1000L) // suspend for 1 second
print("InfoWorld!") // Print after delay
}
print("Hello,") // The main coroutine continues
}
We’ve only scratched the surface of Kotlin’s abilities, but these examples should give you an idea of why it’s become so popular with developers. As a mainline language, Kotlin has vastly increased the power and reach of the JVM.
Also see: Kotlin for Java developers.
Scala
Scala differentiates itself from other JVM languages by making functional programming foundational and implementing it rigorously. As a result, developers who prefer functional programming and want to leverage the JVM often turn to Scala. Although it’s not emphasized, Scala also has strong support for object-oriented programming.
Scala is very popular for large-scale, high-throughput, realtime data processing. It is the language of Apache Spark, the distributed platform for big data streaming, batching, analytics, machine learning, and more. Spark’s extensive and excellent use of Scala’s ability to tie together streams of events with functional operators is another powerful driver for Scala adoption.
Pattern matching is one of Scala’s most popular functional programming features. Here’s an example of Scala’s switch-like syntax for flow control:
case class Message(sender: String, body: String)
val notification: Any = Message("Ada Lovelace", "Hello, InfoWorld!")
notification match {
case Message(sender, body) => println(s"Message from $sender: $body")
case "Ping" => println("Received a Ping")
case _ => println("Unknown notification type")
}
This provides a branch if notification is a message type and allows us to define a function that receives the properties of that message. If notification is a String containing “Ping”, it goes to the second case, and the underscore character defines the default. The beauty of this construct is that it all happens within the functional programming paradigm.
Scala also emphasizes immutability, another tenet of functional programming. Immutability makes for simpler software that is less prone to errors. In Scala, the main variable declaration keyword is val, which is a constant, and built-in collections like List, Vector, and Map are all immutable. You modify the collections using functional operations like filter, which create new collections.
Scala is also very strong in concurrency, employing actors in a powerful, reactive-style programming system. Scala’s actor model forms the basis of the renowned Akka framework, a set of libraries for multithreaded, distributed computing.
Scala also has a sophisticated type system that supports advanced use cases. Here’s an example of the trait type, which combines an abstract class and interface. The trait type allows classes to descend from multiple ancestors with both abstract and concrete members:
trait Speaker {
def speak(): String
def announce(message: String): Unit = {
println(message)
}
}
class Dog extends Speaker {
override def speak(): String = "Woof!"
}
class Person(name: String) extends Speaker {
override def speak(): String = s"Hello, my name is $name."
}
@main def main(): Unit = {
val sparky = new Dog()
val ada = new Person("Ada")
println(s"The dog says: ${sparky.speak()}")
println(s"The person says: ${ada.speak()}")
ada.announce("I am learning about traits!")
}
Notice that the Speaker trait has both concrete and abstract methods, and classes that extend it can extend more than one trait, which is not possible with an abstract class.
There is more to Scala, of course, but these examples give you a taste of it.
Groovy
Groovy is the original JVM alternative. It is a highly dynamic scripting language popular for its simple, low-formality syntax. It’s the language of the ubiquitous Gradle build manager, and is often used as a glue language, or when an application needs customizable extension points. It is also well-regarded for its ability to define DSLs.
For developers coming from Java, Groovy feels like a version of Java that has some of the boilerplate and formality removed. Groovy is in the main a superset of Java, meaning most Java is also valid Groovy.
Groovy is also the language of the Spock test framework.
Groovy dispenses with the “unnecessary” semicolons, and it automatically provides undeclared variables for scripts (known as script binding). This is especially handy for application extensions and DSLs, where the host language (particularly Java) creates a context for the Groovy script and users can create functionality without declaring variables.
This example offers a taste of Groovy’s streamlined flavor:
def list = [1, 2, 3, 4, 5]
def doubled = list.collect { it * 2 }
println("Doubled: " + doubled) //-> Doubled: [2, 4, 6, 8, 10]
def evens = list.findAll { it % 2 == 0 }
println("Evens: " + evens) //-> Evens: [2, 4]
Here, you can see Groovy’s low-formality collection handling, which is based on functional programming.
Another of Groovy’s popular features is its dynamic, optional typing. You can declare a variables type, but you don’t have to. If you don’t declare the variable type, Groovy will manage the variable based on how it is being used, a technique known as ducktyping. (JavaScript has a similar operation.)
Finally, Groovy supports metaprogramming, which is something like a more powerful version of the Java reflection API.
Clojure
Last but not least, Clojure is a descendent of Lisp, a foundational language used in machine learning and symbolic processing. Lisp has influenced many languages and holds a special place for language buffs, thanks to its unique blend of expressive yet simple syntax and “code as data” philosophy.
Code as data, also known as homoiconicity, means the code is represented as data structures in the language. This opens up metaprogramming opportunities because the code representation can be loaded and manipulated directly as software.
Code as data also creates possibilities for powerful macros, where the macro understands the code syntax it expands. This approach to macros is different from languages like C, where macros are simple text, often leading to sneaky errors.
Here’s a simple function in Clojure’s Lisp-like syntax:
;; Comments in Clojure use double semi-colons
(defn greet [name]
(str "Hello, " name "!"))
The parenthetically enclosed blocks you see are a feature of the code also being data structures. Parentheses denote a collection (a list) and functions are defined and called using a list (e.g., keywords, function names, arguments).
Clojure is also known for its strong concurrency model, being built from the ground up to simplify state management across multiple threads. Clojure’s focus on immutability and excellent support for managed state transitions make it a well-rounded concurrent language. It focuses on immutability instead of orchestrating mutable state between threads, which would leave room for errors. Clojure also includes a reactive agent model for dealing with mutable state and concurrency.
Clojure is a highly structured and refined language. It is rigorously functional in its philosophy and delivers a significant power to the developer. These qualities in Clojure’s design and execution have made it a well-respected choice among programmers.
Conclusion
The four languages described here are the stars of the JVM alternative languages universe, but there are many others. In particular, there are JVM versions of mainstream languages, such as jRuby and Jython.
Kotlin has become a full-blown mainstream language in its own right and has recently entered the Tiobe top 20. But all four languages bring strengths in particular areas. And they all demonstrate the power of the JVM itself.
Here’s a look at the high-level characteristics of the four languages:
| Language | Paradigm | Learning curve | Killer use case | Core values |
| Kotlin | OOP, functional (pragmatic) | Easy | Android Apps | Pragmatism, safety |
| Scala | Functional, OOP (rigorous) | Moderate | Big data (Spark) | Type safety, scalability |
| Clojure | Functional (Lisp) | Hard | Data-centric APIs | Simplicity, immutability |
| Groovy | Dynamic, scripting | Easy | Builds (Gradle) | Flexibility, scripting |

