Kotlin was built to replace Java. Or at least, supersede it for a wide range of Android development tasks. Released by JetBrains in 2016, it is designed to address some of Java’s drawbacks and provide a smoother, faster alternative for devs everywhere.
So really the question in the title is redundant, right? Kotlin is just better, surely? Well, actually it’s much more nuanced than that. Java, with its extensive and established ecosystem, is still extremely reliable for a variety of platforms, particularly legacy systems, while Kotlin features a modern and concise syntax that minimizes boilerplate and potential bugs.
Thus, when weighing up the merits of Java vs Kotlin, it’s really about what you’re building, and what you prefer to work with. In this article, we’ll attempt to give you some more context, so you can make the most informed choice possible.
Table of Contents
Introduction
Kotlin
Kotlin is a modern but already mature and open-source programming language. It’s concise, safe, interoperable with Java and other languages, and provides many ways to reuse code between multiple platforms for productive programming. It is statically-typed and supports both object-oriented and functional programming. Its syntax and features are similar to those of other languages like C#, Java, and Scala.
Java
Java is an open-source, general-purpose, object-oriented programming language. Being a multiplatform language, Java can run on almost any device, operating system, or server. Since it is compiled to bytecode, it can run on any Java Virtual Machine (JVM).
Java is statically typed, meaning it performs type checking at compile time. Its syntax is similar to C and C++, though it offers fewer low-level features.
Top-line comparison of Kotlin and Java
Popularity
Java is much better-known than Kotlin, simply because it’s been around longer.
In the past 12 months, Google Trends shows that Java has a search interest score of 84, while Kotlin only has a score of 6. This means Java is searched for a lot more than Kotlin.
Usage
Kotlin
- Android Development: Preferred for modern Android app development due to its concise syntax and advanced features, and Google’s official support.
- Server-Side Development: Increasingly used for server-side applications thanks to its modern features and seamless interoperability with Java.
- Multiplatform Projects: Suitable for projects that target multiple platforms (e.g., JVM, JavaScript, WASM, native) using Kotlin Multiplatform.
Java
- Enterprise Applications: Widely used for building robust, scalable server-side applications in large organizations.
- Android Development: Long-time preferred language for developing Android apps.
- Web Development: Commonly used for creating dynamic web applications using frameworks like Spring.
- Big Data: Utilized in big data technologies and platforms such as Apache Hadoop and Apache Spark.
Java remains a strong choice for enterprise and legacy systems, while Kotlin offers modern features that enhance productivity and is highly favored for new Android projects and multiplatform development.
Syntax Comparison
💡 Kotlin is like a concise poem, while Java is like a verbose essay.
Here is a simple example to compare the syntax of a basic “Hello, World!” program in both Java and Kotlin.
Java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Kotlin:
fun main() {
println("Hello, World!")
}
Both programs perform identical tasks, but the Kotlin program glides gracefully with a touch of readability.
Let’s delve into more code examples to further explore the differences between the two languages.
Function declaration
Let’s compare the function declarations in Java and Kotlin for a function with two Int
parameters and an Int
return type.
Java:
public class Main {
public static void main(String[] args) {
int result = sum(5, 3);
System.out.println("The sum is: " + result);
}
public static int sum(int a, int b) {
return a + b;
}
}
Kotlin:
fun main() {
val result = sum(5, 3)
println("The sum is: $result")
}
// Standard function declaration
fun sum(a: Int, b: Int): Int {
return a + b
}
// Function body as an expression with inferred return type
fun sum(a: Int, b: Int) = a + b
Comparison:
- Java: Requires an explicit return type and
public static
modifiers, or the creation of an instance of a class if you don’t want to usestatic
. - Kotlin: Uses the
fun
keyword with explicit parameter types. The return type is optional.
Summary:
Kotlin functions are shorter, support simpler expression bodies, and don’t need explicit return types, reducing extra code compared to Java.
Variable declaration
Kotlin:
- Immutable Variables (
val
): Assigned once, cannot be reassigned.
val x: Int = 5
// x = 5 (cannot change)
- Mutable Variables (
var
): Can be reassigned.
var y: Int = 5
y += 1
// y = 6
- Type Inference: Kotlin automatically determines the variable type.
val z = 5
// z = 5 (type inferred as Int)
Java:
- Immutable Variables: Java uses
final
keyword for constants.
final int x = 5;
// x = 5 (cannot change)
- Mutable Variables: Standard variable declarations can be reassigned.
int y = 5;
y += 1;
// y = 6
- Type Inference: Not supported in Java
Summary:
- Kotlin: Uses
val
for immutable variables andvar
for mutable variables. Supports type inference. - Java: Uses
final
for constants. Standard variable declarations for mutable variables. Type must be explicitly declared.
Creating Classes and Instances
Defining a Class
Kotlin: Use the class
keyword.
class Shape
Java: Use the class
keyword.
public class Shape {
}
Class Properties
Kotlin: Properties can be listed in the declaration or body.
class Rectangle(val height: Double, val length: Double) {
val perimeter = (height + length) * 2
}
Java: Properties (fields) are declared inside the class body.
public class Rectangle {
private double height;
private double length;
public Rectangle(double height, double length) {
this.height = height;
this.length = length;
}
public double getPerimeter() {
return (height + length) * 2;
}
}
Constructor
Kotlin: Default constructor with parameters in the class declaration.
fun main() {
val rectangle = Rectangle(5.0, 2.0)
println("The perimeter is ${rectangle.perimeter}")
}
Java: Explicitly defined inside the class body.
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(5.0, 2.0);
System.out.println("The perimeter is " + rectangle.getPerimeter());
}
}
Inheritance
Kotlin: Declared by a colon (:). Classes are final by default. You should use open
to allow inheritance.
open class Shape
class Rectangle(height: Double, length: Double) : Shape() {
val perimeter = (height + length) * 2
}
Java: Declared with extends
. All classes are inheritable by default.
public class Shape {
}
public class Rectangle extends Shape {
private double height;
private double length;
public Rectangle(double height, double length) {
this.height = height;
this.length = length;
}
public double getPerimeter() {
return (height + length) * 2;
}
}
Summary:
- Kotlin:
- Concise class and property declarations.
- Default constructors with parameter lists.
open
keyword for inheritance.
- Java:
- Explicit class and property declarations.
- Explicit constructor methods.
- Uses
extends
for inheritance, all classes are inheritable by default.
String Templates
Kotlin:
Kotlin uses string templates to embed variables and expressions directly in strings.
var a = 1
// Simple name in template
val s1 = "a is $a"
// Output: "a is 1"
a = 2
// Arbitrary expression in template
val s2 = "${s1.replace("is", "was")}, but now is $a"
// Output: "a was 1, but now is 2"
Java:
Java requires string concatenation or String.format
for similar functionality.
int a = 1;
// Simple name in template
String s1 = "a is " + a;
// Output: "a is 1"
a = 2;
// Arbitrary expression in template
String s2 = s1.replace("is", "was") + ", but now is " + a;
// Output: "a was 1, but now is 2"
Summary:
- Kotlin:
- String Templates: Uses
$
for variable names and${}
for expressions. - Concise and Readable: More concise and readable.
- String Templates: Uses
- Java:
- String Concatenation: Uses
+
for concatenation. - String.format: Can use
String.format
for more complex cases. - Verbose: More verbose compared to Kotlin.
- String Concatenation: Uses
Conditional Expressions
Kotlin:
Kotlin allows if
to be used as an expression.
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
// If used as an expression
fun maxOf(a: Int, b: Int) = if (a > b) a else b
Java:
Java uses the traditional if-else
statement, and the ternary operator for expressions.
public class Main {
public static int maxOf(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
// Ternary operator
public static int maxOf(int a, int b) {
return (a > b) ? a : b;
}
public static void main(String[] args) {
int result = maxOf(3, 5);
System.out.println("The maximum value is " + result);
}
}
Summary:
- Kotlin:
- If as an Expression:
if
can be used both as a statement and as an expression. - Concise: Allows for more concise code when using
if
as an expression.
- If as an Expression:
- Java:
- If Statement: Traditional
if-else
statements. - Ternary Operator: Uses
? :
for expressions, which can be more concise but is less versatile than Kotlin’s approach.
- If Statement: Traditional
💡 Note: The ternary operator is specific to Java and not available in Kotlin.
Loops
Kotlin:
- For Loop:
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
- While Loop:
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
Java:
- For Loop:
List<String> items = Arrays.asList("apple", "banana", "kiwifruit");
for (String item : items) {
System.out.println(item);
}
for (int index = 0; index < items.size(); index++) {
System.out.println("item at " + index + " is " + items.get(index));
}
- While Loop:
List<String> items = Arrays.asList("apple", "banana", "kiwifruit");
int index = 0;
while (index < items.size()) {
System.out.println("item at " + index + " is " + items.get(index));
index++;
}
Summary:
- Kotlin:
- For Loop: Supports iterating over items and indices.
- While Loop: Simple syntax for while loops.
- Java:
- For Loop: Enhanced for loop (
for-each
) and traditional for loop. - While Loop: Standard while loop syntax.
- For Loop: Enhanced for loop (
When Expression (Switch-Case)
Kotlin:
Kotlin uses the when
expression for conditional checks, which can replace the traditional switch-case or if-else chains.
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
Java:
Java uses a switch-case statement for similar conditional checks. Starting from Java 12, switch expressions are also available but are less versatile than Kotlin’s when
.
public String describe(Object obj) {
if (obj instanceof Integer && obj.equals(1)) {
return "One";
} else if (obj instanceof String && obj.equals("Hello")) {
return "Greeting";
} else if (obj instanceof Long) {
return "Long";
} else if (!(obj instanceof String)) {
return "Not a string";
} else {
return "Unknown";
}
}
Summary:
- Kotlin:
- When Expression: More powerful and versatile, handling multiple types and conditions in a concise manner.
- Java:
- If-Else Chain: Uses traditional if-else chains for complex conditions.
- Switch-Case Statement: Uses switch-case for simpler conditional checks, with switch expressions introduced in newer versions.
Community Support: Java vs. Kotlin
- Java:
- Java has been around for a long time and has a massive community. This translates into a wealth of resources, libraries, frameworks, and essential tools for developers. Almost any issue can be readily solved, due to the extensive community support.
- Kotlin:
- Kotlin is relatively new but its community is rapidly growing. There are many libraries and frameworks available for Kotlin development, with support from Google and other companies. Kotlin’s official website, along with online communities like Stack Overflow and GitHub, provides ample resources for learning and troubleshooting.
Language Features
Let’s compare the features of each language to dive further into our comparison guide. This will help us understand the strengths and nuances of Kotlin and Java in various aspects of development.
What Java Has That Kotlin Does Not
- Checked Exceptions: Java enforces handling of exceptions at compile-time.
- Primitive Types: Java has explicit primitive types (e.g.,
int
,char
,double
) that are not objects, allowing for performance optimization in low-level operations. - Static Members: Java uses static members directly; Kotlin uses companion objects or top-level functions.
- Wildcard Types: Java supports wildcard types in generics; Kotlin uses declaration-site variance and type projections.
- Ternary Operator: Java uses the ternary operator (
a ? b : c
); Kotlin usesif
expressions. - Records: Java has introduced records for immutable data carriers.
- Pattern Matching: Java has pattern matching for instance checks.
- Package-Private Visibility: Java has package-private visibility, which is absent in Kotlin.
What Kotlin Has That Java Does Not
- Lambda Expressions + Inline Functions: Kotlin allows you to write concise and efficient custom control structures, using lambda expressions and inline functions, which can improve performance by reducing overhead.
- Extension Functions: Kotlin lets you add new functionality to existing classes without modifying their code, making it easier to extend and enhance existing libraries and frameworks.
- Null-Safety: Built-in null safety to avoid null pointer exceptions.
- Smart Casts: Automatic type casting after null checks.
- String Templates: Simplifies string concatenation (Java 21 has a preview of this feature).
- Properties: Kotlin provides first-class support for properties, allowing you to define and access properties directly, making code more readable and reducing the boilerplate associated with getter and setter methods.
- Primary Constructors: More concise syntax for constructors.
- First-Class Delegation: Simplifies delegation pattern.
- Type Inference: Kotlin can infer types for variables and properties, reducing the need for explicit type declarations.
- Singletons: Easily create single instances.
- Declaration-Site Variance & Type Projections: For safer generics.
- Range Expressions: For cleaner loop expressions.
- Operator Overloading: Allows custom implementations of operators.
- Companion Objects: For static-like members.
- Data Classes: Reduces boilerplate for classes that primarily hold data, automatically generating common methods like
equals()
,hashCode()
, andtoString()
. - Coroutines: Simplifies asynchronous programming.
- Top-Level Functions: Allows functions to be defined outside of classes, supporting a more functional programming style.
- Default Arguments: Allows default values for function parameters.
- Named Parameters: Improves readability by allowing parameters to be passed by name.
- Infix Functions: Allows calling functions without parentheses or dots.
- Expect and Actual Declarations: For multiplatform development.
- Explicit API Mode: Better control of API exposure.
Summary
Java provides robust features such as checked exceptions, primitive types, and static members, making it ideal for large-scale enterprise applications. Kotlin, however, introduces modern programming conveniences like null-safety, extension functions, and coroutines, offering a more concise and expressive syntax that streamlines development.
Performance Comparison: Kotlin vs. Java
Kotlin and Java both compile to bytecode that runs on the Java Virtual Machine (JVM), resulting in comparable performance. Here are some key points regarding their performance:
- Bytecode Compatibility: Since both Kotlin and Java compile to JVM bytecode, their execution speed is similar.
- Inline Functions: Kotlin’s inline functions can provide performance optimizations. Inline functions can reduce the overhead of function calls by embedding the function’s code directly at the call site, which can be beneficial in performance-critical sections of code.
- Code Conciseness: Kotlin’s more concise syntax can lead to fewer lines of code, potentially improving maintainability and readability.
Summary
Kotlin and Java both run on the JVM, so their performance is roughly comparable. However, Kotlin’s inline functions can provide performance optimizations in some cases. Overall, performance should not be the deciding factor when choosing between Kotlin and Java. The choice should be based on other factors such as language features, development speed, and code maintainability. Both languages offer robust performance, and the JVM ensures efficient execution for both.
Kotlin brings modern programming features that can lead to cleaner and safer code, while Java’s mature ecosystem and extensive libraries make it a reliable choice for enterprise applications.
Other considerations
Mixing Java and Kotlin – Interoperability
Kotlin provides the first-class interoperability with Java, and modern IDEs make it even better.
Adding Java code to an existing Kotlin code
You can consume the Java class from Kotlin or vice versa without any further actions.
For example, adding the following Java class:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void greet() {
System.out.println("Hello, " + name);
}
}
lets you call it from Kotlin like any other type in Kotlin.
val person = Person("Alice")
println(person.name)
person.greet()
Kotlin Multiplatform
Kotlin Multiplatform simplifies cross-platform development by allowing you to write common code once and share it across different platforms. This approach saves time on coding and maintenance while still benefiting from native programming capabilities.
Kotlin Multiplatform use cases
- Android and iOS applications Kotlin Multiplatform is ideal for building mobile applications that share code between Android and iOS. This approach helps in implementing common features like networking, data storage, data validation, analytics, and more, without duplicating efforts for each platform.
- Multiplatform libraries Library authors can use Kotlin Multiplatform to create libraries with shared code and platform-specific implementations for JVM, web, and native platforms. These libraries can then be used as dependencies in other cross-platform projects.
- Desktop applications Compose Multiplatform allows sharing UIs across desktop platforms like Windows, macOS, and Linux. Many applications, including the JetBrains Toolbox app, use this approach to simplify development and maintenance.
- Kotlin Wasm With Kotlin, you can build web applications and reuse mobile and desktop UIs through Compose Multiplatform and Kotlin/Wasm, making it a versatile choice for modern web development.
To sump up
In this blog post, we compared Kotlin and Java in terms of syntax, features, and performance, delving deep to provide a thorough understanding of both languages. After our comparison, we concluded that both languages have their strengths, but Kotlin stands out with its concise, expressive, and safer syntax.
The best language for you will depend on your specific needs and preferences. If you are looking for a language with a large community and a wide range of features, then Java is a good choice. If you prefer a language with modern syntax and features, then Kotlin is a good option.