Logo
blank Skip to main content

Comparison of Kotlin and Java for Reactive Programming

Reactive programming allows us to develop flexible, independent, and scalable solutions โ€” if we choose the right tools. In this article, we explore whether Java or Kotlin is more suitable for writing asynchronous code for Android and Java Virtual Machine (JVM) applications. We explain the pros and cons of these languages and compare their effectiveness for reactive programming with ReactiveX.

This article will be useful for developers who want to improve their reactive programming skills and choose the best toolset for Android development.

Reactive programming in Kotlin and Java

Reactive programming allows developers to create responsive, flexible, sustainable, and message-driven applications. This type of programming relies on asynchronous data streams that can contain all sorts of static and dynamic data: variables, user input, cache data, data structures, etc. With reactive programming, if data in a stream changes, all elements that use that stream will take those changes into account.

Reactive programming is especially useful for:

  • large development teams where developers write functionalities independently
  • projects that contain massive volumes of data
  • multi-user projects
  • artificial intelligence and machine learning algorithms.

When using reactive programming principles to create Android applications, developers usually choose between Java and Kotlin. Letโ€™s see why these languages are so attractive.

Java is an object-oriented programming language created more than 20 years ago that was the go-to choice for decades. Until recently, it also was the only possible option for Android development. Most of Javaโ€™s success can be explained by its core principles:

  • Simplicity, which comes from a lack of low-level facilities and automatic memory management
  • A robust toolset in the form of libraries and frameworks that make it easy and quick to develop new programs
  • Portability between JVMs, as a compiled Java program doesnโ€™t depend on computer hardware and can be executed in any JVM environment
  • High performance due to features like just-in-time compilation and generational garbage collection

On the other hand, being an old C-like language, Java has several drawbacks when used for reactive programming:

  • Lack of clarity. Even though Java offers rudimentary type inference when declaring local variables or creating templated values, this isnโ€™t enough to keep clear the reactive streams that operate on a rich domain of complex data.
  • Weak modeling options when compared to Kotlin.
  • Requires a lot of boilerplate code to model concepts that Kotlin offers out of the box.
  • Requires the RxJava library, which extends Javaโ€™s capabilities for composing asynchronous and event-based programs for JVMs. The Java Development Kit (JDK) 8 has several features for reactive programming, but they arenโ€™t enough for comfortable development.
Pros and cons of Java for reactive programming

Kotlin is a statically typed programming language developed for the JVM. It provides developers with ways of writing more concise, robust, and fun code compared to Java. This language became popular because of characteristics like:

  • Java interoperability, allowing developers to call Java functions from Kotlin code. The .kt files containing Kotlin source code can coexist with .java files in one project and compile just fine.
  • Versatility, as Kotlin compiles to bytecode and can run on any compatible JVM, can be transpiled into JavaScript, and can be compiled to Native code using a Low Level Virtual Machine.
  • Null safety, which eliminates null references. In Kotlin, any variable is non-nullable by default, but you can assign the null value using String? syntax.
  • ะกonvenient features such as type inference, concise and expressive syntax, and a rich built-in library of utility functions make development faster and easier.

But as a relatively new language, Kotlin also has several disadvantages:

  • Slower compilation compared to Java.
  • Smaller developer community, which can be an issue when you need to find a solution for a rare issue. Kotlin documentation, however comprehensive, doesnโ€™t contain answers to all questions.
  • Synchronization-related bugs may appear if you use both dedicated APIs for reactive programming (such as ReactiveX) and built-in coroutines for asynchronous programming.
Pros and cons of Kotlin for reactive programming

Kotlin can also use RxJava for reactive programming. Thereโ€™s a dedicated RxKotlin library, but it consists mostly of additional functions like subscribeBy. Core reactive functions for reactive programming can be called from RxJava. Also, Kotlin has built-in functionality for asynchronous programming.

Thanks to the abovementioned advantages, Kotlin has quickly become a popular language. In 2019, Google even declared it a preferred language for Android development. But is it really that good? Check out our function-to-function comparison of Java and Kotlin to find out.

Now, letโ€™s see how ReactiveX works and how it can help you write applications in Java or Kotlin.

Read also:
7 Kotlin Features You Should Start Using Right Now

Key principles of ReactiveX

ReactiveX is an API for asynchronous programming with observable streams. It provides developers with all the tools needed for reactive programming, transforming the information within a program into data streams. Data-carrying messages (or events) in these streams originate from various Observable (or Flowable) objects and are consumed by Subscriber objects.

Data stream management in ReactiveX

The ReactiveX API provides multiple tools for controlling event streams and transforming the data that moves through these streams from Observables to Subscribers. Data carried by events can be transformed by the map function before it reaches the subscriber. Itโ€™s also possible to combine or separate event streams using functions like zip and merge.

These transformations, as well as the actions performed by Subscribers, can be carried out asynchronously thanks to a variety of Schedulers. You can use Schedulers to make sure that code will be safely executed in the corresponding thread pool.

ReactiveX tools also allow dependencies between objects to be looser by implementing an Event Bus โ€” a boilerplate publisher/subscriber implementation that organizes communication between program components in such a way that they can react to any event in the program without being dependent on other components. This instrument is especially useful when the lifecycles of components are independent.

For example, an Activity of an Android application can gather a user’s input, create a message object, and push it into an Event Bus. This message will then be processed by whatever is listening to such events. With an Event Bus, the Activity that posted the message may be closed before the message is processed and it wonโ€™t affect the processing routine. The result can be posted to the same or to a parallel Event Bus.

Event Bus operation

ReactiveX is especially helpful for large development teams, as it makes merge conflicts impossible. Developers can all agree on the set of data streams that should exist within the program, define the format for messages, and then implement functionality independently.

Even though ReactiveX is a powerful and useful tool, however, it should be used wisely. Events in ReactiveX have temporal characteristics, meaning that messages come in a particular order and at a certain time. The general rule of thumb is that if data doesnโ€™t have temporal characteristics, it probably should not be passed through reactive streams. For example, a pure function that only takes an input and produces an output without fetching additional data from anywhere should not be augmented with reactive functionality.

A downside of using ReactiveX features is the possibility to introduce unnecessary complexity and bugs into the program. Itโ€™s best to use simple function calls whenever possible to avoid synchronization problems and keep code clear and simple.

With this in mind, letโ€™s compare the capabilities of Java and Kotlin with ReactiveX and the RxJava library.

Working with ReactiveX: Java vs Kotlin

Let’s say we want to write a program that receives commands from a remote server and executes them. The commands come in the form of messages received as raw strings and have to be parsed and represented as objects in the programโ€™s data model. To compare the effectiveness of Kotlin and Java for this task, weโ€™ll provide code in both languages.

The implementation seems straightforward. We just have to implement Connection, Parser, and Handler classes. The Connection class will receive the raw messages, parse them using the Parser‘s functionality, and then pass them to the Handler, which will do the actual work.

But how flexible will this system be? What if the connection type changes? Or the program has to receive commands and handle messages from multiple connections? To accommodate these scenarios, the set of commands may expand, and the Handler class will have to be split into multiple Handlers, thus bloating all the Connection classes.

A good way of organizing communication between the Connection and Handler implementations in this case is to use an Event Bus.

Related services

Custom Mobile App Development Services

Step 1: Defining an interface for in-program communication

Messages constitute a well-defined closed set. They can be represented in the form of a tagged union โ€” a data structure that allows for creating a closed set of options that a value of that type can have. In Kotlin, we can implement commands in the form of messages using sealed classes:

Kotlin
sealed class Command {
  
    object MakeCoffee: Command()
  
    data class LaunchMissiles(val missileType: MissileType): Command()
  
    class DoaBarrelRoll: Command() { 
        fun barrelRoll() { 
            // ... 
        }
    }
  
}

Any class can inherit anything from a sealed class โ€” even another sealed class. The only limitation is that all the classes have to be located in the same file. This allows the compiler and developers to know exactly how many possible outcomes there are and to handle them accordingly.

In Java, we can try to achieve the same result by somewhat abusing the visibility modifiers and doing the compilerโ€™s work:

Kotlin
abstract class Command {
  
    private Command() {}
  
    public final static class MakeCoffee extends Command {}
  
    public final static class LaunchMissiles extends Command {
        private MissileType missileType;
  
        LaunchMissiles(MissileType missileType) {
            this.missileType = missileType;
        }
  
        public void getMissileType() {
            return this.missileType;
        }
          
        public boolean equals(LaunchMissiles another) {
            return this.missileType == another.missileType;
        }
  
        public String toString() {
            return this.getClass().getSimpleName() + "(" + "missileType=" + missileType.name() + ")";
        }
  
        LaunchMissiles copy() {
            return new LaunchMissiles(this.missileType);
        }
  
        LaunchMissiles copy(MissileType missileType) {
            return new LaunchMissiles(missileType);
        }
    }
  
    public final static class DoaBarrelRoll extends Command {
        void barrelRoll() {
            // ...
        }
    }
  
}

Itโ€™s impossible to inherit from the Command class outside the file itโ€™s declared in. But the code above still isnโ€™t going to accomplish everything that the Kotlin example does. For example, we canโ€™t use Java’s switch statement the same way as Kotlin’s when expression to match and handle all possible variants. In Kotlin, handling will look like this:

Kotlin
when (command) {
    is Command.MakeCoffee -> // ...
    is Command.LaunchMissiles -> // ...
    is Command.DoaBarrelRoll -> // ...
}

In this case, Kotlin’s when command isnโ€™t only syntactic sugar but a way of verifying that all cases are handled. If they arenโ€™t handled, the program wonโ€™t even compile.

To do this in Java, weโ€™ll have to use cascades of if statements and verbosely check the type of command instance. For example:

Java
if(command instanceof Command.MakeCoffee) {
    ...
} else if(command instanceof Command.LaunchMissiles) {
    ...
} else if(command instanceof Command.DoaBarrelRoll) {
    ...
}

Having defined the set of commands, we can create an Event Bus to pass these messages through. The bus will be implemented as a wrapper over the PublishSubject class object, which can be both an Observable and a Subscriber. The wrapper class is going to provide two methods: sink for posting messages and source for receiving them.

Since the Bus object is going to be used by multiple classes at the same time, we have to make sure theyโ€™re using the same Bus object by creating either a static or a singleton class. In Kotlin, we can use the object keyword to easily declare singleton classes:

Kotlin
object Bus {
    private val subject = PublishSubject.create<Command>()
    val source = subject.toFlowable(BackpressureStrategy.BUFFER)
    fun sink(cmd: Command) = subject.onNext(cmd)
}

The Java implementation is a little more verbose:

Kotlin
public class Bus { 
    static private PublishSubject<Command> subject = PublishSubject.create();
    static Flowable<Command> source() {
        return subject.toFlowable(BackpressureStrategy.BUFFER);
    }
    static void sink(Command cmd) {
        subject.onNext(cmd);
    }
}

The code snippets above may seem equivalent, but the Java code has a flaw. If null is passed to the sink method, the program will crash because RxJava methods donโ€™t allow null values in their arguments. In Kotlin, all types are non-nullable by default if not specified otherwise. Consider the following lines:

Kotlin
val regularCommand: Command = // ...
val nullableCommand: Command? = // ...

These two variables have distinct types: Command and Command?. Itโ€™s impossible to assign the Command? value to a variable of the Command type because Command? may not contain anything. The compiler guarantees that all values of the Command type are well defined and can be used safely. And this is exactly why the Kotlin implementation of the Bus object is completely safe. Implementing it in Java requires additional null checks.

After fixing issues with null values, weโ€™re done defining the in-program communication interface.

Step 2: Working with Connections and Handlers

As we mentioned earlier, the implementation of an application can be easily parallelized between developers because Connections only post parsed messages to the Bus object and Handlers only read messages from it.

In Kotlin, implementing the Connection class looks like this:

Kotlin
class Connection { 
    // ... 
    private fun onMessageReceived(msg: String) = 
        Parser(msg)?.let { parsedMessage -> 
            Bus.sink(parsedMessage) 
        } ?: // ...
    // ... 
}

Parser is a function that can convert a raw string into an instance of Command?. We use Kotlin’s ?. syntax on the parser‘s result to filter the non-null values and post them to the Bus object. If the parser fails to convert the received string, it will return null. This case will be handled by the code following the so-called Elvis operator (?:).

Compare this to the Java implementation:

Kotlin
class Connection {
    // ...
    private void onMessageReceived(String msg) {
        Command parsedMessage = Parser.parse(msg);
        if (parsedMessage != null) {
            Bus.sink(parsedMessage);
        } else {
            // ...
        }
    }
    // ...
}

Now we need to implement Handlers. Our implementations in Kotlin and Java do the same thing: subscribe to the Bus, filter the objects they can handle, and then process the values that pass through the filter. In Kotlin, adding Handlers looks like this:

Kotlin
class Handler { 
    // ... 
    private val disposable = Bus.source
            .filter { /* filtering predicate */ }
            .subscribe(::handle) 
    // ... 
    private fun handle(cmd: Command) { 
        // ... 
    } 
    // ... 
}

Hereโ€™s the same implementation in Java:

Kotlin
public class Handler { 
    // ... 
    private Disposable disposable = Bus.source()
                .filter( (cmd) -> /* filtering predicate */)
                .subscribe(this::handle);
    // ... 
    private void handle(Command cmd) { 
        // ... 
    } 
    // ... 
}

To achieve this behavior conveniently and concisely, we used the Flowable.filter method in both examples above. It takes a predicate function as an argument and applies it to all the values received from upstream. A value is passed downstream only if its predicate is evaluated as true. Then we use the Flowable.subscribe method to add a custom callback to the upstream Flowable object. Flowable.subscribe also takes a function as an argument. This function is invoked whenever an event reaches the subscriber. The event’s value is passed to the function as an argument.

With that, our program is finished both in Kotlin and Java. We have implemented a system where each component depends only on the communication interface โ€” the Bus object. All it takes to extend such a system is to implement the required functionality and configure its relationships with the Bus.

Comparing the implementations in both languages, we can see that the one written in Kotlin has several advantages:

  • It requires less code (and, therefore, less time) to implement the needed functionality.
  • Thereโ€™s no need to perform null checks.
  • Thereโ€™s explicit support for the functional programming paradigm, which means there are tagged unions, first-class functions, algebraic types, and more.
  • Out-of-the-box data classes require next to no effort to define the programโ€™s object domain.
  • Built-in support for singleton classes makes it easy to share functionality and states between different parts of the program.

Check out our other article to learn how to develop an application to send and receive SMS in Android.

Read also:
Pentesting Android Applications: Tools and Step-by-Step Instructions

Conclusion

Reactive programming is becoming more and more popular in various areas of development, including in Android development. This approach allows for managing vast amounts of data, reducing complicated passages of code to a couple of lines, creating independent modules, and more. But in order to experience reactive programming in its full glory, you need to choose the right programming language.

Modern languages like Kotlin offer developers the best tools borrowed from different programming paradigms. Rust, Golang, Swift, and Kotlin each combine the flexibility of the traditional imperative style of programming with the robustness and expressiveness of the functional paradigm.

Even though Java is undergoing a modernization process, itโ€™s still far behind Kotlin in terms of flexibility. Moreover, using Java for Android development deprives developers of lots of modern features, since Android supports only JDK 7 along with some JDK 8 features. And features for reactive programming are only supported in JDK 8.

At Apriorit, we have an expert team of Android developers that can create solutions of any complexity written in Kotlin, Java, and many other programming languages. Contact us if you have a challenging task in mind!

Have a question?

Ask our expert!

Tell us about your project

Send us a request for proposal! Weโ€™ll get back to you with details and estimations.

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Book time slot

Contact us