Press "Enter" to skip to content

How to use callbacks in Java

A callback operation in Java is a function that is passed to another function and executed after some action is complete. A callback can be executed synchronously or asynchronously. in the case of a synchronous callback, one function is executed immediately after another. In the case of a asynchronous callbacka function is executed after an indeterminate period of time and occurs in no particular sequence with other functions.

This article introduces you to callbacks in Java, starting with the classic example of the callback as listener in the Observable design pattern. You’ll see examples of a variety of synchronous and asynchronous callback implementations, including a functional callback using CompletableFuture.

Synchronous callbacks in Java

A synchronous callback function will always be executed right after performing some action. That means it will be synchronized with the function that performs the action.

As I mentioned, an example of a callback function is in the Observable design pattern. In a UI that requires a button click to initiate some action, we can pass the callback function as a listener on that button click. The listener function waits until the button is clicked, then executes the listener callback.

Now let’s look at some examples of the callback concept in code.

Anonymous inner class callback

Anytime we pass an interface with a method implementation to another method in Java, we are using the concept of a callback function. In the following code, we will pass the Consumer functional interface and an anonymous inner class (unnamed implementation) to implement the accept() method.

once the accept() the method is implemented, we will execute the action from the performAction method; then we will execute the accept() method of Consumer Interface:


import java.util.function.Consumer;

public class AnonymousClassCallback {

  public static void main(String[] args) {
    performAction(new Consumer<String>() {
      @Override
      public void accept(String s) {
        System.out.println(s);
      }
    });
  }

  public static void performAction(Consumer<String> consumer) {
    System.out.println("Action is being performed...");
    consumer.accept("Callback is executed");
  }

}

The output of this code is the print statement:


Action is being performed... 

Callback is executed...

In this code, we pass the Consumer interface to the performAction() method, then invoked the accept() method after the action is finished.

You may also notice that using an anonymous inner class is quite verbose. It would be much better to use a lambda instead. Let’s see what happens when we use the lambda for our callback function.

lambda callback

In Java, we can implement the functional interface with a lambda expression and pass it to a method, then execute the function after an operation completes. This is how it looks in the code:


public class LambdaCallback {

  public static void main(String[] args) {
    performAction(() -> System.out.println("Callback function executed..."));
  }

  public static void performAction(Runnable runnable) {
    System.out.println("Action is being performed...");
    runnable.run();
  }

}

Once again, the result indicates that the action is being performed and the callback is being executed.

In this example, you may notice that we pass the Runnable functional interface on the performAction method. Thus, we were able to override and execute the run() after action method performAction method is finished.

Asynchronous callbacks

Often, we want to use an asynchronous callback method, which means a method that will be called after the action but asynchronously with other processes. That might help performance when the callback method doesn’t need to be invoked immediately after the other process.

Simple thread callback

Let’s start with the simplest way we can perform this asynchronous callback operation. In the following code, we will first implement the run() method of a Runnable functional interface. Then we will create a Thread and use the run() method we just implemented inside the Thread. Finally, we will start the Thread to run asynchronously:


public class AsynchronousCallback {

  public static void main(String[] args) {
    Runnable runnable = () -> System.out.println("Callback executed...");
    AsynchronousCallback asynchronousCallback = new AsynchronousCallback();
    asynchronousCallback.performAsynchronousAction(runnable);
  }

  public void performAsynchronousAction(Runnable runnable) {
    new Thread(() -> {
      System.out.println("Processing Asynchronous Task...");
      runnable.run();
    }).start();
  }

}

The output in this case is:

Also Read:  Visual Studio Code 1.77 previews GitHub Copilot Chat

Processing Asynchronous Task...

Callback executed...

Notice in the code above that we first create an implementation for the run() method of Runnable. So we invoke the performAsynchronousAction() method, passing the runnable functional interface with the run() method implementation.

Within performAsynchronousAction() we passed the runnable interface and implement the other Runnable interface inside the Thread with a lambda. Then we print “Processing Asynchronous Task…” Finally, we invoke the callback function run which we pass as a parameter, printing “Callback executed…”

Asynchronous Parallel Callback

In addition to calling the callback function inside the asynchronous operation, we could also call a callback function in parallel with another function. This means that we could start two threads and call those methods in parallel.

The code will be similar to the previous example, but notice that instead of calling the callback function directly, we’ll start a new thread and call the callback function inside this new thread:


// Omitted code from above…
public void performAsynchronousAction(Runnable runnable) {

    new Thread(() -> {
      System.out.println("Processing Asynchronous Task...");
      new Thread(runnable).start();
    }).start();
  }

The result of this operation is the following:


Processing Asynchronous Task...

Callback executed...

The asynchronous parallel callback is useful when we don’t need the callback function to be executed immediately after the action of the callback. performAsynchronousAction() method.

A real world example would be when we buy a product online and we don’t need to wait until payment is confirmed, stock is verified and all those heavy loading processes. In that case, we can do other things while the callback invocation is running in the background.

CompletableFuture callback

Another way to use an asynchronous callback function is to use the CompletableFuture API. This powerful API, introduced in Java 8, makes it easy to execute and combine asynchronous method calls. It does everything we did in the previous example, like creating a new Thread then start and manage it.

In the following code example we will create a new CompletableFuturethen we will invoke the supplyAsync method passing a String.

Next, we’ll create another one,CompletableFuture that will thenApply a callback function to execute with the first function we set up:


import java.util.concurrent.CompletableFuture;

public class CompletableFutureCallback {

  public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture
        = CompletableFuture.supplyAsync(() -> "Supply Async...");

    CompletableFuture<String> execution = completableFuture
        .thenApply(s -> s + " Callback executed...");

    System.out.println(execution.get());
  }

}

The output here is:


Supply Async... Callback executed…

Conclusion

Callbacks are ubiquitous in software development, widely used in tools, design patterns, and applications. Sometimes we use them without even realizing it.

We’ve gone through a variety of common callback implementations to help demonstrate their utility and versatility in Java code. Here are some features of callbacks to remember:

  • A callback function is supposed to be executed when another action is executed or in parallel to that action.
  • A callback function can be synchronouswhich means it should be executed immediately after the other action without any delay.
  • A callback function can be asynchronouswhich means it can run in the background and it may take some time for it to run.
  • The Observable design pattern uses a callback to notify interested entities when an action has occurred.

Copyright © 2023 IDG Communications, Inc.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *