Quarkus 3: The Future of Java Microservices With Virtual Threads and Beyond
Learn how Quarkus integrates the virtual thread for Java developers to run blocking applications with a single @RunOnVirtualThread annotation.
Join the DZone community and get the full member experience.
Join For FreeOver the past four years, developers have harnessed the power of Quarkus, experiencing its transformative capabilities in evolving Java microservices from local development to cloud deployments. As we stand on the brink of a new era, Quarkus 3 beckons with a promise of even more enhanced features, elevating developer experience, performance, scalability, and seamless cloud integration.
In this enlightening journey, let’s delve into the heart of Quarkus 3's integration with virtual threads (Project Loom). You will learn how Quarkus enables you to simplify the creation of asynchronous concurrent applications, leveraging virtual threads for unparalleled scalability while ensuring efficient memory usage and peak performance.
Journey of Java Threads
You might have some experience with various types of Java threads if you have implemented Java applications for years. Let me remind you real quick how Java threads have been evolving over the last decades. Java threads have undergone significant advancements since their introduction in Java 1.0. The initial focus was on establishing fundamental concurrency mechanisms, including thread management, thread priorities, thread synchronization, and thread communication. As Java matured, it introduced atomic classes, concurrent collections, the ExecutorService
framework, and the Lock
and Condition
interfaces, providing more sophisticated and efficient concurrency tools.
Java 8 marked a turning point with the introduction of functional interfaces, lambda expressions, and the CompletableFuture
API, enabling a more concise and expressive approach to asynchronous programming. Additionally, the Reactive Streams
API standardized asynchronous stream processing and Project Loom introduced virtual threads, offering lightweight threads and improved concurrency support.
Java 19 further enhanced concurrency features with structured concurrency constructs, such as Flow
and WorkStealing
, providing more structured and composable concurrency patterns. These advancements have significantly strengthened Java's concurrency capabilities, making it easier to develop scalable and performant concurrent applications. Java threads continue to evolve, with ongoing research and development focused on improving performance, scalability, and developer productivity in concurrent programming.
Virtual threads, generally available (GA) in Java 21, are a revolutionary concurrency feature that addresses the limitations of traditional operating system (OS) threads. OS threads are heavyweight, limited in scalability, and complex to manage, posing challenges for developing scalable and performant concurrent applications.
Virtual threads also offer several benefits, such as being a lightweight and efficient alternative, consuming less memory, reducing context-switching overhead, and supporting concurrent tasks. They simplify thread management, improve performance, and enhance scalability, paving the way for new concurrency paradigms and enabling more efficient serverless computing and microservices architectures. Virtual threads represent a significant advancement in Java concurrency, poised to shape the future of concurrent programming.
Getting Started With Virtual Threads
In general, you need to create a virtual thread using Thread.Builder
directly in your Java project using JDK 21. For example, the following code snippet showcases how developers can create a new virtual thread and print a message to the console from the virtual thread. The Thread.ofVirtual()
method creates a new virtual thread builder, and the name()
method sets the name of the virtual thread to "virtual-thread". Then, the start()
method starts the virtual thread and executes the provided Runnable lambda expression, which prints a message to the console. Lastly, the join()
method waits for the virtual thread to finish executing before continuing. The System.out.println()
statement in the main thread prints a message to the console after the virtual thread has finished executing.
public class MyVirtualThread {
public static void main(String[] args) throws InterruptedException {
// Create a new virtual thread using Thread.Builder
Thread thread = Thread
.ofVirtual()
.name("my-vt")
.start(() -> {
System.out.println("Hello from virtual thread!");
});
// Wait for the virtual thread to finish executing
thread.join();
System.out.println("Main thread completed.");
}
}
Alternatively, you can implement the ThreadFactory
interface to start a new virtual thread in your Java project with JDK 21. The following code snippet showcases how developers can define a VirtualThreadFactory
class that implements the ThreadFactory
interface. The newThread()
method of this class creates a new virtual thread using the Thread.ofVirtual()
method. The name()
method of the Builder object is used to set the name of the thread and the factory()
method is used to set the ThreadFactory
object.
// Implement a ThreadFactory to start a new virtual thread
public class VirtualThreadFactory implements ThreadFactory {
private final String namePrefix;
public VirtualThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
return Thread.ofVirtual()
.name(namePrefix + "-" + r.hashCode())
.factory(this)
.build();
}
}
You might feel it will get more complex when you try to run your actual methods or classes on top of the virtual threads. Luckily, Quarkus enables you to skip the learning curve and execute the existing blocking services on the virtual threads quickly and efficiently. Let’s dive into it.
Quarkus Way to the Virtual Thread
You just need to keep reminding yourself of two things to run an application on virtual threads.
- Implement blocking services rather than reactive (or non-blocking) services based on JDK 21.
- Use
@RunOnVirtualThread
annotation on top of a method or a class that you want.
Here is a code snippet of how Quarkus allows you to run the process()
method on a virtual thread.
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
@RunOnVirtualThread
public String hello() {
Log.info(Thread.currentThread());
return "Quarkus 3: The Future of Java Microservices with Virtual Threads and Beyond";
}
}
You can start the Quarkus Dev mode (Live coding) to verify the above sample application. Then, invoke the REST endpoint using the curl command.
$ curl http://localhost:8080/hello
The output should look like this.
Quarkus 3: The Future of Java Microservices with Virtual Threads and Beyond
When you take a look at the terminal, you see that Quarkus dev mode is running. You can see that a virtual thread is created to run this application.
(quarkus-virtual-thread-0) VirtualThread[#123,quarkus-virtual-thread-0]/runnable@ForkJoinPool-1-worker-1
Try to invoke the endpoint a few more times, and the logs in the terminal should look like this.
You learned how Quarkus integrates the virtual thread for Java developers to run blocking applications with a single @RunOnVirtualThread
annotation. You should be aware that this annotation is not a silver bullet for all use cases. In the next article, I’ll introduce pitfalls, limitations, and performance test results against reactive applications.
Opinions expressed by DZone contributors are their own.
Comments