eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

1. Overview

In this tutorial, we’ll compare two ways to handle asynchronous operations in Java. First, we’ll see how the sleep() method works on Threads. Then, we’ll try to achieve the same functionality with the features provided by the Awaitility library. Along the way, we can see how these solutions compare and which is more suitable for our use case.

2. Use Cases

The sleep() and await() methods can be particularly useful when we’d like to wait for the completion of an asynchronous operation. For example, our application might send messages to a message broker or queue. In this case, we don’t know precisely when the message is received on the other end. Another use case can be calling an API endpoint and waiting for a specific result. For example, we send a request to a service, it starts a long-running task, and we wait for it to finish.

In our example application, we’ll create a simple service that tracks the status of our requests. We’ll check if a request is in the required state after a given amount of time.

3. Application Setup

Let’s create an asynchronous service that handles the requests. We also need a way to fetch the status of these requests to be able to verify it afterward:

public class RequestProcessor {

    private Map<String, String> requestStatuses = new HashMap<>();

    public String processRequest() {
        String requestId = UUID.randomUUID().toString();
        requestStatuses.put(requestId, "PROCESSING");

        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.schedule((() -> {
            requestStatuses.put(requestId, "DONE");
        }), getRandomNumberBetween(500, 2000), TimeUnit.MILLISECONDS);

        return requestId;
    }

    public String getStatus(String requestId) {
        return requestStatuses.get(requestId);
    }

    private int getRandomNumberBetween(int min, int max) {
        Random random = new Random();
        return random.nextInt(max - min) + min;
    }
}

This service uses Java’s ScheduledExecutorService to delay a command that changes the status of the request to “DONE”. It waits a random amount of time between one half-second and two seconds.

We’ll create unit tests to showcase the two approaches for checking the async operation’s result.

4. Plain Java

First, let’s use the plain Java approach and pause the execution on the thread.

In this case, we can set the amount of time we’d like to wait in milliseconds. Let’s create our test class and the first unit test:

@DisplayName("Request processor")
public class RequestProcessorUnitTest {

    RequestProcessor requestProcessor = new RequestProcessor();

    @Test
    @DisplayName("Wait for completion using Thread.sleep")
    void whenWaitingWithThreadSleep_thenStatusIsDone() throws InterruptedException {
        String requestId = requestProcessor.processRequest();

        Thread.sleep(2010);

        assertEquals("DONE", requestProcessor.getStatus(requestId));
    }
}

In this test case, we call the processRequest() method of our RequestProcessor to start a request. Then, we have to wait before getting the status via the request ID. We’re waiting for a status change because we expect it to be done.

When we use Thread.sleep(), we have to make sure that we wait enough time before checking the result. In our case, we know that our request is processed in a maximum of two seconds. In real life, though, it’s more difficult to figure out the correct amount of time to wait.

5. Awaitility

We can also use Awaitility, a library that provides an easy-to-read API for testing this kind of code.

First of all, we add the awaitility dependency to our pom.xml:

<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.2.0</version>
    <scope>test</scope>
</dependency>

Now, we can create our test case that utilizes the new functionalities:

@Test
@DisplayName("Wait for completion using Awaitility")
void whenWaitingWithAwaitility_thenStatusIsDone() {
    String requestId = requestProcessor.processRequest();

    Awaitility.await()
      .until(() -> requestProcessor.getStatus(requestId), not(equalTo("PROCESSING")));

    assertEquals("DONE", requestProcessor.getStatus(requestId));
}

This test case starts the same way as the previous one. But, instead of sleeping for a fixed two seconds, we have a conditional statement. In this case, we sleep until the request isn’t in “PROCESSING” status anymore. After this, we use the same assertion to make sure that the status has the expected value.

We can provide additional options, too. For example, we can configure that we’d like to wait at most two seconds plus the time amount of one more poll, to make sure the process is completed, and awaitility poll has gotten the updated “DONE” value.

Awaitility.await()
  .atMost(2101, TimeUnit.MILLISECONDS)
  .until(() -> requestProcessor.getStatus(requestId), not(equalTo("PROCESSING")));

In the background, Awaitility uses polling to check whether the given statement is true or false. We can increase or decrease the poll interval, but the default value is 100 milliseconds. In other words, Awaitility checks the condition every 100 milliseconds. Below, let’s add a poll delay of 500 milliseconds:

Awaitility.await()
  .atMost(2501, TimeUnit.MILLISECONDS)
  .pollDelay(500, TimeUnit.MILLISECONDS)
  .until(() -> requestProcessor.getStatus(requestId), not(equalTo("PROCESSING")));

6. Comparison

As we saw, both approaches can work just fine for our use case. However, there are some advantages and disadvantages that we should be aware of.

It’s fairly simple to use sleep() to pause a thread, but we don’t have much control after we send it to sleep. The operation that we’re waiting for might finish immediately afterward and we’d still have to wait for the whole pre-defined duration.

On the other hand, Awaitility lets us have a more fine-grained configuration. As soon as the condition’s fulfilled, the thread resumes execution, and this can improve performance.

The sleep() method is available in Java by default, while Awaitility is a library that needs to be added to our project. We have to consider this when choosing the solution. It’s more obvious to use the built-in method, but we can have much more readable code with the domain-specific language.

7. Conclusion

In this article, we discussed two different approaches for handling asynchronous operations in Java. We focused on testing, but these examples can be used in other parts of the code as well.

First, we used Java’s built-in solution to pause execution on a thread with the sleep() method. It’s easy to use, but we have to provide the sleep duration in advance.

Then, we compared it with the Awaitility library, which provides a domain-specific language for handling this kind of situation. It results in more readable code, but we have to learn how to use it.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook Jackson – NPI EA – 3 (cat = Jackson)