In the enterprise world, managing resource-intensive tasks has always been challenging, particularly when using synchronous blocking operations. With the release of Java 21, features like Virtual Threads bring a new dimension to managing concurrency with minimal resource consumption.
Jakarta EE 10, in tandem with Java 21, allows for easy asynchronous execution providing better concurrency control, and also enhanced HTTP/2 support. Recently I had an opportunity to refactor a traditional Java EE 8 codebase to take advantage of these new capabilities. Let’s see how –
Consider this Stateless EJB method –
import javax.ejb.Stateless;
import javax.ejb.Asynchronous;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Stateless
@Path("/apm")
public class ReportService {
@GET
@Path("/metric")
public String getAMetric() {
String metric = performAnExpensiveOperation();
return metric;
}
private String performAnExpensiveOperation() {
// Simulating a long-running operation
// like fetching data from a DB or an external service
try {
Thread.sleep(10000); // Block the thread for 10 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
return "{ 'Metric' : 12345.50 }";
}
}
While @Asynchronous annotation was introduced in EJB 3.1 as part of the Java EE 6 specification, which was released in 2009, the code in question wasn’t using this annotation. This annotation allows for asynchronous method invocation in stateless and singleton session beans which enables developers to execute long-running tasks without blocking the calling thread. This makes it very useful for tasks that do not return an immediate result, such as report generation or complex calculations. However, the thread running the long running operation continues to be blocked from processing other requests.
Using Java 21’s java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor() allows us to create a virtual thread per request, meaning that even resource-intensive operations won’t block traditional threads. This approach allows the expensive operation to run truly asynchronously. The request handling thread can go back to serving other requests and get back to the current request once the long running operation is completed.
Most Jakarta EE 10 application servers support HTTP/2 allowing for asynchronous communication. Payara has been supporting HTTP/2 since version 5. Hence combining all three, the browser can now receive updates as soon as the operation completes, and the non-blocking nature improves overall responsiveness. The server and application code can now also serve more requests in the same amount of time.
Let’s look at the refactored EJB code now.
import jakarta.ejb.Stateless;
import jakarta.ejb.Asynchronous;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@Stateless
@Path("/apm")
public class ReportService {
@GET
@Path("/metric")
@Asynchronous
public CompletionStage<String> getAMetric() {
return CompletableFuture.supplyAsync(() -> performAnExpensiveOperation())
.thenApplyAsync(metric -> "Report generated: " + metric);
}
private String performExpensiveOperation() {
// Simulate an expensive operation with Virtual Threads (Java 21)
try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// Simulating long operation
try {
Thread.sleep(10000); // Simulate delay
} catch (InterruptedException e) {
e.printStackTrace();
}
return "{ 'Metric' : 12345.50 }";
}).join();
}
return "{ 'Metric' : -1.00 }";
}
}
Not only the performance increased with this refactoring but also the overall responsiveness of the endpoint improved by a huge factor.
Stay tuned for more Jakarta EE 10 and Java Features.


Leave a Reply