How to do Asynchronous Programming With Java

  • Post last modified:December 15, 2022
  • Reading time:4 mins read

Introduction

  • When we write code generally we end up writing synchronized code most of the time. 
  • But in many situations, it makes sense not to block the execution and execute the code asynchronously.
  • In this article, we will consider one use case where writing asynchronous code makes sense.

Use Case

  • Consider a user apply for a product such as a credit card or debit card from front-end UI.
  • Now once at the backend, we receive user requests, we perform n number of use cases such as validation, transformation, persist to DB and sending acknowledgment email, etc.
  • Now all of these processes don’t have to be synchronous. For example, we can send emails to customers irrespective of saving them to the database.
  • So we can execute this piece of logic asynchronously.

Product Type

  • Product type is enum that represents the types of product a company offers.
package AsyncExcercise;

public enum ProductType {
    CREDIT_CARD,
    DEBIT_CARD,
    NOW_ACCOUNT,
    SAVINGS_ACCOUNT,
    LOAN;
}

User POJO

  • Let’s create a user POJO that we will receive from client.
package AsyncExcercise;

public class User {

    private int userId;
    private String name;
    private Enum productType;
    private String timeStamp;
    private String emailId;

    public User(int userId, String name, Enum productType, String timeStamp, String emailId) {
        this.userId = userId;
        this.name = name;
        this.productType = productType;
        this.timeStamp = timeStamp;
        this.emailId = emailId;
    }

    public String getEmailId() {
        return emailId;
    }

    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Enum getProductType() {
        return productType;
    }

    public void setProductType(Enum productType) {
        this.productType = productType;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", name='" + name + '\'' +
                ", productType=" + productType +
                ", timeStamp='" + timeStamp + '\'' +
                '}';
    }
}

Apply Logic

  • Now once the client sends us the user object, we perform various operations on it.
  • First thing we do is check if a user is valid, if it is then we proceed to execute the sendMail logic asynchronously and then saves user to database.
// submit userRequest
private static void apply(User user){
        if(validate()){
            // send thank you mail to the customer
            sendMail(user); // non blocking operation

            // save to database
            savetoDB(user); //
        }

    }

Send Email

  • Now, send email is implemented with CompletableFuture that takes supplier as an argument, we ran exceptionally to handle cases where we face some exceptions in send call.
  • If send call is successful then it prints success message otherwise exception is printed.
  • We are adding sleep time to see log of saveDB printed before sendMail to understand async behavior
private static void sendMail(User user){
         CompletableFuture.supplyAsync(() -> send(user))
                .exceptionally(throwable -> throwable.getMessage())
                .thenAccept(System.out::println);
    }

    private static String send(User user){
        try {
            Thread.sleep(1000);
        } catch (Exception ex){
            throw  new RuntimeException("Thread Interrupted");
        }
        // mail sender logic
        if(!user.getEmailId().isEmpty()){
            // send email
            return "SUCCESS ! email sent !";
        }
        throw new RuntimeException("FAILED ! email sent failed :(");
    }

Validate

  • Validate logic is just a mock behavior that returns true;l
   private static boolean validate(){

        // validation
        return true;
    }

Save to DB

  • Save DB is also mock behavior to persist data into DB.
    private static void savetoDB(User user) {
        // jpa persit;
        System.out.println("persisted to db");
    }

Client code

  • In client code we are creating two user , one has email address and other just has empty email.
    public static void main(String[] args) throws InterruptedException {
        User user = new User(1,
                "sam",
                ProductType.CREDIT_CARD,
                new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new Date()),
                "abc@gmail.com");

        User user1 = new User(1,
                "sam",
                ProductType.CREDIT_CARD,
                new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new Date()),
                "");

        apply(user);
        apply(user1);

        Thread.sleep(3000);
    }

Console Log

  • As we can see email log is printed later than the db log, which means email logic is executed asynchronously.

Conclusion

  • In this article, we wrote asynchronous code that executes without blocking our execution and saves on performance.
  • Java provides a Completable class to implement asynchronous behavior

Bonus Tip

Leave a Reply