1. Overview

JDeferred is a small Java library (also supports Groovy) used for implementing asynchronous topology without writing boilerplate code. This framework is inspired by the Jquery’s Promise/Ajax feature and Android’s Deferred Object pattern.

In this tutorial, we’ll show how to use JDeferred and its different utilities.

2. Maven Dependency

We can start using JDeferred in any application by adding the following dependency into our pom.xml:

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-core</artifactId>
    <version>1.2.6</version>
</dependency>

We can check the latest version of the JDeferred project in the Central Maven Repository.

3. Promises

Let’s have a look at a simple use-case of invoking an error-prone synchronous REST API call and perform some task based on the data returned by the API.

In simple JQuery, the above scenario can be addressed in the following way:

$.ajax("/GetEmployees")
    .done(
        function() {
            alert( "success" );
        }
     )
    .fail(
        function() {
            alert( "error" );
        }
     )
    .always(
        function() {
            alert( "complete" );
        }
    );

Similarly, JDeferred comes with the Promise and Deferred interfaces which register a thread-independent hook on the corresponding object that triggers different customizable action based on that object status.

Here, Deferred acts as the trigger and the Promise acts as the observer.

We can easily create this type of asynchronous workflow:

Deferred<String, String, String> deferred
  = new DeferredObject<>();
Promise<String, String, String> promise = deferred.promise();

promise.done(result -> System.out.println("Job done"))
  .fail(rejection -> System.out.println("Job fail"))
  .progress(progress -> System.out.println("Job is in progress"))
  .always((state, result, rejection) -> 
    System.out.println("Job execution started"));

deferred.resolve("msg");
deferred.notify("notice");
deferred.reject("oops");

Here, each method has different semantics:

  • done() – triggers only when the pending actions on the deferred object is/are completed successfully
  • fail() – triggers while some exception is raised while performing pending action/s on the deferred object
  • progress() – triggers as soon as pending actions on the deferred object is/are started to execute
  • always() – triggers regardless of deferred object’s state

By default, a deferred object’s status can be PENDING/REJECTED/RESOLVED. We can check the status using deferred.state() method.

Point to note here is that once a deferred object’s status is changed to RESOLVED, we can’t perform reject operation on that object.

Similarly, once the object’s status is changed to REJECTED, we can’t perform resolve or notify operation on that object. Any violation will result into an IllegalStateExeption.

4. Filters

Before retrieving the final result, we can perform filtering on the deferred object with DoneFilter.

Once the filtering is done, we’ll get the thread-safe deferred object:

private static String modifiedMsg;

static String filter(String msg) {
    Deferred<String, ?, ?> d = new DeferredObject<>();
    Promise<String, ?, ?> p = d.promise();
    Promise<String, ?, ?> filtered = p.then((result) > {
        modifiedMsg = "Hello "  result;
    });

    filtered.done(r > System.out.println("filtering done"));

    d.resolve(msg);
    return modifiedMsg;
}

5. Pipes

Similar to filter, JDeferred offers the DonePipe interface to perform sophisticated post-filtering actions once the deferred object pending actions are resolved.

public enum Result { 
    SUCCESS, FAILURE 
}; 

private static Result status; 

public static Result validate(int num) { 
    Deferred<Integer, ?, ?> d = new DeferredObject<>(); 
    Promise<Integer, ?, ?> p = d.promise(); 
    
    p.then((DonePipe<Integer, Integer, Exception, Void>) result > {
        public Deferred<Integer, Exception, Void> pipeDone(Integer result) {
            if (result < 90) {
                return new DeferredObject<Integer, Exception, Void>()
                  .resolve(result);
            } else {
                return new DeferredObject<Integer, Exception, Void>()
                  .reject(new Exception("Unacceptable value"));
            }
    }).done(r > status = Result.SUCCESS )
      .fail(r > status = Result.FAILURE );

    d.resolve(num);
    return status;
}

Here, based on the value of the actual result, we’ve raised an exception to reject the result.

6. Deferred Manager

In a real time scenario, we need to deal with the multiple deferred objects observed by multiple promises. In this scenario, it’s pretty difficult to manage multiple promises separately.

That’s why JDeferred comes with DeferredManager interface which creates a common observer for all of the promises. Hence, using this common observer, we can create common actions for all of the promises:

Deferred<String, String, String> deferred = new DeferredObject<>();
DeferredManager dm = new DefaultDeferredManager();
Promise<String, String, String> p1 = deferred.promise(), 
  p2 = deferred.promise(), 
  p3 = deferred.promise();
dm.when(p1, p2, p3)
  .done(result -> ... )
  .fail(result -> ... );
deferred.resolve("Hello Baeldung");

We can also assign ExecutorService with a custom thread pool to the DeferredManager:

ExecutorService executor = Executors.newFixedThreadPool(10);
DeferredManager dm = new DefaultDeferredManager(executor);

In fact, we can completely ignore the use of Promise and can directly define the Callable interface to complete the task:

DeferredManager dm = new DefaultDeferredManager();
dm.when(() -> {
    // return something and raise an exception to interrupt the task
}).done(result -> ... )
  .fail(e -> ... );

7. Thread-Safe Action

Although, most of the time we need to deal with asynchronous workflow, some of the time we need to wait for the results of the all of the parallel tasks.

In this type of scenario, we may only use Object‘s wait() method to wait for all deferred tasks to finish:

DeferredManager dm = new DefaultDeferredManager();
Deferred<String, String, String> deferred = new DeferredObject<>();
Promise<String, String, String> p1 = deferred.promise();
Promise<String, String, String> p = dm
  .when(p1)
  .done(result -> ... )
  .fail(result -> ... );

synchronized (p) {
    while (p.isPending()) {
        try {
            p.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

deferred.resolve("Hello Baeldung");

Alternatively, we can use Promise interface’s waitSafely() method to achieve the same.

try {
    p.waitSafely();
} catch (InterruptedException e) {
    e.printStackTrace();
}

Although both of the above methods perform pretty much the same thing, it’s always advisable to use the second one since the second procedure doesn’t require synchronization.

8. Android Integration

JDeferred can be easily integrated with Android applications using the Android Maven plugin.

For APKLIB build, we need to add the following dependency in the pom.xml:

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-android</artifactId>
    <version>1.2.6</version>
    <type>apklib</type>
</dependency>

For AAR build, we need to add the following dependency in the pom.xml:

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-android-aar</artifactId>
    <version>1.2.6</version>
    <type>aar</type>
</dependency>

9. Conclusion

In this tutorial, we explored about JDeferred, and it’s different utilities.

As always, the full source code is available over on GitHub.