This article compares Java vs. Groovy vs. Scala vs. Kotlin on the basis of several language features using code examples. For basic syntax, compilation & execution comparison refer to Java vs. Groovy, Scala, Kotlin – Basic Syntax Comparison of JVM Languages
Features Summary
This is a quick table of summary of comparison. Scroll down further for in depth examples & actual code comparison.
Feature | Java | Groovy | Scala | Kotlin |
---|---|---|---|---|
Non-object Primitives | ✓ | ✓ | ✗ | ✗ |
Type Inference | ✓ Since Java 10 |
✓ | ✓ | ✓ |
Traits / Interface with abstract methods | ✓ Since Java 8 |
✓ | ✓ | ✓ |
Closures | ✓ Since Java 8 |
✓ | ✓ | ✓ |
Duck Typing | ✗ | ✓ | ✗ | ✗ |
Null safety | ✗ | ✓ | ✗ | ✓ |
Higher order functions | ✓ Since Java 8 |
✓ | ✓ | ✓ |
Inline function | ✗ | ✗ | ✗ | ✓ |
Operator overloading | ✗ | ✓ | ✓ | ✓ |
Non-object primitives
Java & Groovy does have primitives which are not really objects. There are separate wrapper object classes available for the each primitive type. On the contrast, Scala & Kotlin does not have primitives at all. All number types are objects. In other words, everything is object in Scala & Kotlin.
Scala:
1 2 3 4 |
val x: Long = 12345678 println(x); val intx: Int = 123 println(intx); |
Kotlin:
1 2 3 4 |
val x: Long = 12345678 println(x); val intx: Int = 123 println(intx); |
Type Inference / Optionally typed
Type inference means automatic detection of data type instead of statically typed data type.
Java:
In java, type of variable must be defined i.e. type inference was not supported. Like in below example String type is defined for variable s. From Java 10 onwards, Java supports type inference using ‘var’ Example
1 2 3 4 |
String s = "Its All Binary!"; // Before Java 10 System.out.println(s); var name = "Name"; // Java 10 onward |
Groovy:
1 2 |
def s = "Its All Binary!" println s |
Scala:
1 2 |
def s = "Its All Binary!" println(s) |
Kotlin:
1 2 |
val s = "Its All Binary!" println(s) |
Traits / Interface with default methods
Traits are interfaces with method implementation/method body.
Java
Before Java 8, this was not supported. From Java 8, interfaces support default methods. Example
1 2 3 4 5 6 7 8 9 10 11 |
interface Traits{ public default void printMethod(){ System.out.println("This is default implementation"); } } public class TraitsImpl implements Traits{ public static void main(String[] args){ new TraitsImpl().printMethod(); } } |
Groovy
1 2 3 4 5 6 7 |
trait Traits { String printMethod() { println "This is default implementation" } } class TraitsImpl implements Traits {} def b = new TraitsImpl(); b.printMethod(); |
Scala
1 2 3 4 5 6 7 8 9 10 11 |
trait Traits extends Any { def printMethod(): Unit = println("This is default implementation") } class TraitsImpl(val i: Int) extends AnyVal with Traits object Demo { def main(args: Array[String]) { val t = new TraitsImpl(1) t.printMethod() } } |
Kotlin
1 2 3 4 5 6 7 8 9 10 11 12 13 |
interface Traits { fun printMethod() { println("This is default implementation") } } class TraitsImpl : Traits { } fun main(args: Array<String>) { var t = TraitsImpl(); t.printMethod(); } |
Closures
Closures are the instance of a method/function which can be passed to method as argument or returned from a method.
Java:
Before Java 8, this was not supported. From Java 8, in the form of lambda this is supported. Still lambda is dependent on functional interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Anonymous classes imitating closure like behavior java.util.function.BiFunction<Integer, Integer, Integer> func2 = new java.util.function.BiFunction<>(){ public Integer apply(Integer a, Integer b){ return a + b; } }; System.out.println(func2.apply(2,3)); // Lambda creating Anonymous class imitating closure like behavior java.util.function.BiFunction<Integer, Integer, Integer> func = (a,b) -> a + b; System.out.println(func.apply(2,3)); |
Groovy:
1 2 |
def clos = {a, b -> a + b}; println clos.call(2,3); |
Scala:
1 2 |
val fun = (a:Int, b:Int) => a + b println(fun(2,3)); |
Kotlin:
1 2 |
val func = { a: Int, b: Int -> a + b}; println(func(2,3)) |
Duck typing
Duck typing is way to determine behavior of class on basis of method presence instead of type. So type does not determine at compile time if certain behavior is suitable or not. At runtime, method presence is checked in object & executed or else error. Duck typing is only supported in Groovy. Others i.e. Java, Kotlin, Scala doesn’t support duck typing.
Groovy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Human{ def run(){println "Human running !"} } class Animal{ def run(){println "Animal running !"} } class Rock{ } def h= new Human(); h.run() def a= new Animal(); a.run() def r= new Rock(); r.run() // Compiles fine, fails at run time |
1 2 3 |
Human running ! Animal running ! Caught: groovy.lang.MissingMethodException: No signature of method: Rock.run() is applicable for argument types: () values: [] |
Scala
Scala doesn’t allow duck typing & catches it at compile time itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
object Duck { def main(args: Array[String]): Unit = { var h = new Human(); h.run(); var a = new Human(); a.run(); var r = new Rock(); r.run(); } } class Human { def run(): Unit = { println("Human running !") } } class Animal { def run(): Unit = { println("Animal running !") } } class Rock { } |
1 2 3 4 5 |
>scalac Duck.scala Duck.scala:8: error: value run is not a member of Rock r.run(); ^ one error found |
Null Safety
Groovy & Kotlin does provide few ways to gracefully prevent NullPointerException as shown in below exception. Java & Scala do not have features to specifically prevent NullPointerException & developers are expected to handle it explicitly.
Groovy:
Null safe access operator ?. which is equivalent to
if(p not null) then p.employer else null
aa
1 2 3 4 5 6 7 8 9 10 |
// Null safe access operator ?. Won't cause NPE even though p is null class Person{ Employer employer; } class Employer{ String name; } Person p println p?.employer?.name |
1 |
null |
Scala:
No null safety. Standard null checks needed like Java.
1 2 3 |
var p = new Person(); // First initialize p, else we get " error: '=' expected but ';' found." p = null; // Make p null println(p.name); |
1 2 3 |
java.lang.NullPointerException at Test$.main(Test.scala:11) at Test.main(Test.scala) |
Kotlin:
Compile time check for null assignment.
1 2 3 4 5 6 |
var p = Person(); // First initialize, else we get compiler error "this variable must either have a type annotation or be initialized" p = null; // Make p null to get null reference. class Person{ var name: String = ""; } |
1 2 3 |
> kotlinc Test.kt error: null can not be a value of a non-null type Person p = null; |
Higher order functions
Higher order functions are methods/functions that take other method/functions as method arguments.
Java:
Since Java 8, java is adopting functional programming. Higher order functions are not directly available, but indirectly can be implemented using functional interfaces like Function. These functional interfaces wrap behavior within an object which can be passed to other methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import java.util.function.*; public class HigherOrder{ public static void main(String[] args){ new HigherOrder().higherOrderFunc(a -> "Method arg is " + a); } // This is higher order function which takes other Function as arg. public void higherOrderFunc(Function<String,String> func){ System.out.println(func.apply("Test-Arg")); } } |
Groovy
1 2 3 4 5 6 7 |
def closureInst = {println "Pass this to higher order function"}; def higherOrdFunc(Closure closr){ closr.call(); } higherOrdFunc(closureInst); |
Scala:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Higher order function which takes another function func is argument def operator(func: (Int, Int)=> Int,a: Int,b: Int): Int = { return func(a, b); } def main(args: Array[String]): Unit = { // Call higher order function operator by passing sum function. val sum = (a:Int, b:Int) => a + b println(operator(sum, 3, 7)); // Call higher order function operator by passing multiply function. val multiply = (a:Int, b:Int) => a * b println(operator(multiply, 3, 7)); } |
Kotlin:
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) { higherOrderFunc { s1 -> s1 + "Hello World" } } fun higherOrderFunc(function: (String) -> String){ val s: String = function("Appended in higher order func - ") println(s) } |
Inline functions
Inline function instructs compiler to insert complete body code of the function wherever that function is called in code. This is a lightweight alternative to higher order functions which calls for creation of object wherever they are used. With inline function, code is placed at call site at compile time. This is generally used for smaller behaviors.
Kotlin supports inline functions. Couldn’t see any such support in Groovy, Scala & Java.
Kotlin
1 2 3 4 5 6 7 8 9 |
fun main(args: Array<String>) { printStartEnd{ doSomething() }; } // This is inline function inline fun printStartEnd( body: () -> Unit) { println("Method started"); body(); println("Method completed"); } |
1 2 3 |
Method started Doing Something Method completed |
Operator overloading
Operator overloading is a way to add additional purpose for operators depending on its arguments.
Groovy
Groovy has special methods like plus() which can be overridden to implement operator behavior for given class’ objects. Reference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Identifier { int id Identifier(int id) { this.id = id } Identifier plus(Identifier other) { return new Identifier(this.id + other.id) } } def b1 = new Identifier(2) def b2 = new Identifier(3) def b3 = b1+b2 println b3.id |
Scala
In Scala operators are not really operators but they are actually methods unlike Java. So basically operator overloading is nothing but defining method/function with name as operator like +. Reference
1 2 3 4 5 6 7 8 9 10 11 12 |
object Test { var id1 = new Identifier(2); var id2 = new Identifier(3); var id3 = id1 + id2; println("added id = " + id3.getId()); } class Identifier(id: Int){ def +(otherId: Identifier) = new Identifier(this.getId() + otherId.getId()) def getId(): Int = id } |
Kotlin
Kotlin compiler translates + operator into plus() method & then execution happens like any other method. Reference
1 2 3 4 5 6 7 8 9 10 11 12 |
fun main(args: Array<String>) { var id1 = Identifier(2); var id2 = Identifier(3); var id3 = id1 + id2; println(id3.id) } data class Identifier(val id: Int) { operator fun plus(increment: Identifier): Identifier { return Identifier(id + increment.id) } } |
See Also:
Java vs. Groovy, Scala, Kotlin – Basic Syntax Comparison of JVM Languages
One comment to this very amazing comparison: You claim that Scala does not support any remediation for null pointer dereferences (“Null safety”). This is not strictly true, since the programming language provides the special keyword ‘Option’ which offers safe “Null”s by using a slightly different approach (https://alvinalexander.com/scala/scala-null-values-option-uninitialized-variables). Maybe put some footnote explaining that.
Good point. Scala Option has features kind of similar to Optional in Java. This definitely helps to achieve null safety if incorporated appropriately within code.
In addition, do note that Scala only allows nulls to maintain compatibility with Java. Standalone, Scala never uses nor encourages null values. So yes, Scala does not gracefully handle nulls, but that’s just because its not supposed to. Any operation in Scala that could fail will return an Optional instead of a potential null. And any Java operation can be wrapped inside an Optional to maintain null safety.