Callbacks in Java and Kotlin

Quite frequently there can be a situation in any project when simply passing a value to a function (method) is not enough. For example, some action should be run asynchronously at the end of processing. Or the function (method) may operate in a different context and may not know what to do with the data passed to it.

In such cases, a very elegant and convenient solution is to use callbacks. Callback is a function (method) that is passed as an argument to another function (method). First, any callback can be a free-form function and does not need to adhere to any interface. However, using existing Java interfaces is very convenient as they are already defined.

Available Java interfaces

There are quite a lot of predefined interfaces, the basic ones are:

  • Consumer<T> (consumes a value of a predefined type and provides no response)
  • Supplier<T> (provides a value of a predefined type without asking for any input parameters)
  • Function<T,R> (requires an input of type T, provides a value of type R)

We will take a look at Consumer<T> and Function<T,R> because Supplier<T> usually can be replaced by a provided value.

Java consumer callback example

In Java a simple value consumer will look like this:

public class PrintingMessageConsumer implements Consumer<String> {

    @Override
    public void accept(final String message) {
        System.out.println("Consumed: " + message);
    }
}

Then we can also define a class to which this consumer will be passed:

public class MessageService {

    public void processMessage(final String message, final Consumer<String> messageConsumer) {
        // do work...
        messageConsumer.accept(message);
    }
}

And in some other place everything will be aligned similar to this:

public class MessageService {
public class Main {

    public static void main(String[] args) {
        final String message = "sample message";
        final MessageService messageService = new MessageService();

        final Consumer<String> messageConsumer = new PrintingMessageConsumer();
        messageService.processMessage(message, messageConsumer);
    }
}

As you would expect, PrintingMessageConsumer will print the following message to the console:

Consumed: sample message

Kotlin consumer callback example

In Kotlin the code that gives the same outcome will look like this:

class PrintingMessageConsumerKt : Consumer<String> {
    override fun accept(message: String) {
        println("Consumed: $message")
    }
}

class MessageServiceKt {
    fun processMessage(message: String, messageConsumer: Consumer<String>) {
        // do work...
        messageConsumer.accept(message)
    }
}

fun main(args: Array<String>) {
    val message = "sample message"
    val messageService = MessageServiceKt()

    val messageConsumer = PrintingMessageConsumerKt()
    messageService.processMessage(message, messageConsumer)
}

Java function callback example

A function callback will be very similar to a consumer callback. First, we need to define a function:

public class IdentityFunction implements Function<String, String> {

    @Override
    public String apply(String message) {
        return message;
    }
}

Then we slightly modify MessageService from the previous example:

public class MessageService {

    public String processMessage(final String message, final Function<String, String> messageFunction) {
        // do work...
        return messageFunction.apply(message);
    }
}

And the Main class:

public class Main {

    public static void main(String[] args) {
        final String message = "sample message";
        final MessageService messageService = new MessageService();

        final Function<String, String> messageFunction = new IdentityFunction();
        final String result = messageService.processMessage(message, messageFunction);
        System.out.println("Identity function result: " + result);
    }
}

This will, of course, produce the following output:

Identity function result: sample message

Kotlin function callback example

Kotlin code will look like this:

class IdentityFunctionKt : Function<String, String> {
    override fun apply(message: String): String = message
}

class MessageServiceKt {
    fun processMessage(message: String, messageFunction: Function<String, String>): String {
        // do work...
        return messageFunction.apply(message)
    }
}

fun main(args: Array<String>) {
    val message = "sample message"
    val messageService = MessageServiceKt()

    val messageFunction = IdentityFunctionKt()
    val result = messageService.processMessage(message, messageFunction)
    println("Identity function result: $result")
}

Simplifications of Kotlin code

Additionally, Kotlin is a very flexible and dynamic language. So, the same outcome can be achieved with as little code as:

fun main(args: Array<String>) {
    val message = "sample message"
    val messageService = MessageServiceKt()

    messageService.processMessage(message, Consumer {
        // do work...
        println("Consumed: $message")
    })

    val result = messageService.processMessage(message, Function {
        // do work...
        message
    })
    println("Identity function result: $result")
}

This includes both consumer and callback and function callback and does not require any additional class creation.

Conclusion

That is all there is regarding callback functions in Java and Kotlin. I would like to point out that callbacks are free-form functions and there is no need to follow certain guidelines or implement a certain interface. However, implementing an interface is a good practice and would be highly appreciated by your colleagues who will try to read and understand your code 🙂 Happy coding!

0