1 of 37

Concurrency Crossroads: Choosing between Reactive Programming and Virtual Threads in Quarkus

Willem Jan Glerum

J-Fall 2025

Conference

2025-11-06

2 of 37

Willem Jan

Glerum

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Principal Software Engineer @ Lunatech
  • Based in Delft, The Netherlands
  • Using Quarkus since 2019

//

2

3 of 37

Quick

Summary

Introduction

Blocking I/O

Reactive Programming

Virtual Threads

Quarkus Demo

Comparison

Conclusions

Presentation / Virtual Threads vs Reactive Programming in Quarkus

01

02

03

04

05

//

06

07

3

4 of 37

Introduction

01

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

4

5 of 37

Timeline

History

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • 1997 -> Green threads
  • 2000 -> Platform threads
  • 2023 -> Virtual Threads
    • Preview in JDK 19 (JEP-425)
    • Released in JDK 21 (JEP-444)
  • ??? -> Structured Concurrency
    • 1st incubator in JDK 19 (JEP-428)
    • 5th preview in JDK 25 (JEP-505)
    • 6th preview in JDK 26 (JEP-525)

//

5

6 of 37

Blocking

02

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

6

7 of 37

I/O

Presentation / Virtual Threads vs Reactive Programming in Quarkus

I/O (Input/Output) is everywhere:

  • Calling a remote service
  • Interacting with a database
  • Sending messages to a queue

//

7

8 of 37

Blocking I/O

Wait forever

Presentation / Virtual Threads vs�Reactive Programming in Quarkus

  • Ask and wait for something
  • Don’t do anything in between

//

8

9 of 37

Platform threads

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Platform threads map to OS threads
    • Roughly need 1 MB stack
    • Relatively costly & slow to create
  • OS takes care of the scheduling

  • We often use fixed size thread pools
    • Web requests
    • DB connection pool
    • Expensive computations
    • Etc.

//

9

10 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

10

11 of 37

Reactive

03

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

11

12 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

https://quarkus.io/guides/quarkus-reactive-architecture

  • Responsive - they must respond in a timely fashion
  • Elastic - they adapt themselves to the fluctuating load
  • Resilient - they handle failures gracefully
  • Message driven - the component of a reactive system interact using messages

//

12

13 of 37

Reactive

Systems

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Non blocking
  • Handle CPU & memory resources more efficiently
  • Single thread can handle multiple requests
  • Only a few event loop threads needed in an application
  • No need to pool them

//

13

14 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

14

15 of 37

Unification

of Imperative and Reactive

Presentation / Virtual Threads vs Reactive Programming in Quarkus

Quarkus allows you to do both blocking and non-blocking code in the same application

//

15

16 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

https://quarkus.io/guides/quarkus-reactive-architecture

//

16

17 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

https://quarkus.io/guides/quarkus-reactive-architecture

//

17

18 of 37

Reactive programming

Your options

Presentation / Virtual Threads vs Reactive Programming in Quarkus

Multiple options available in Quarkus:

  • io.smallrye.mutiny.Uni & io.smallrye.mutiny.Multi (Mutiny)
  • java.util.concurrent.CompletionStage (CompletableFuture)
  • org.reactivestreams.Publisher (RxJava, Akka Streams, and also Mutiny!)

Or use Kotlin coroutines with suspend functions

//

18

19 of 37

Mutiny

Uni & Multi

Presentation / Virtual Threads vs Reactive Programming in Quarkus

Mutiny is a reactive programming library

  • Uni -> emits a single event (item or failure)
  • Multi -> emits multiple events (0 or n items)

Similar to Mono and Flux from Spring WebFlux

//

19

20 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

20

21 of 37

Quarkus

Extensions

Presentation / Virtual Threads vs Reactive Programming in Quarkus

Reactive extensions:

  • Quarkus REST
  • Quarkus REST Client
  • Quarkus Hibernate Reactive
  • Quarkus Reactive Messaging
  • Quarkus Qute (templating)

Find more here:

https://code.quarkus.io

//

21

22 of 37

Virtual

Threads

04

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

22

23 of 37

Virtual Threads

To the rescue

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Cheap to create
  • Use less memory
  • Managed by the JVM
  • Cheap to block
  • You write imperative code
  • Only useful for I/O bound tasks

//

23

24 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

24

25 of 37

Pinning

& synchronized

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Many libraries used synchronized blocks under the hood, for example JDBC libraries.
    • Most libraries have been fixed now
  • Synchronized blocks can cause pinning
  • Native calls can also cause pinning�(JNI, FFM, etc.)

//

25

26 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

26

27 of 37

Pinning

And solutions

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Enable detection via JFR events
    • jdk.VirtualThreadPinned
  • Resolved in JDK 24 with JEP 491
    • https://openjdk.org/jeps/491
  • Or use java.util.concurrent.locks
    • ReentrantLock

//

27

28 of 37

Monopolization

with CPU load

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Can happen when you have CPU bound workloads without I/O.
  • Probably better to offload to a dedicated thread pool.

//

28

29 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

29

30 of 37

Virtual Threads

And testing

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • In Quarkus virtual threads have a name
    • quarkus-virtual-thread-xxx
    • Easier to debug
  • Junit extension to detect pinning
    • io.quarkus.junit5:junit5-virtual-threads
  • Annotations available for tests:
    • @VirtualThreadUnit
    • @ShouldNotPin
    • @ShouldPin(atMost = 1)

//

30

31 of 37

Structured

Concurrency

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Easier to write concurrent code
    • Avoid thread leaks
    • Cancellation strategies
    • etc.
  • 5th preview in JDK 25
    • https://openjdk.org/jeps/505
  • 6th preview in JDK 26
    • https://openjdk.org/jeps/525
  • Release date ???

//

31

32 of 37

Comparison

06

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

32

33 of 37

Presentation / Virtual Threads vs Reactive Programming in Quarkus

Blocking

  • Imperative programming
  • Things execute sequentially
  • Easy to reason about
  • Higher memory usage
  • Limited amount of threads

Reactive

  • Handle multiple requests on the same thread
  • Higher throughput
  • Lower latency
  • Lower memory usage
  • Harder to write and debug with callbacks or reactive code

Virtual Threads

  • Imperative programming
  • Ease to reason about
  • Lower memory usage
  • Slightly less performant
  • Structured concurrency not ready yet

//

33

34 of 37

Conclusions

07

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

34

35 of 37

Conclusions

Which one do you pick?

Presentation / Virtual Threads vs Reactive Programming in Quarkus

  • Blocking for legacy code
  • Reactive programming when you need the full performance and control
  • Virtual threads whenever you want
    • Test first, watch out for the gotchas
    • Leverage structured concurrency when it becomes available
  • Mix and match in Quarkus!

//

35

36 of 37

References

Presentation / Virtual Threads vs Reactive Programming in Quarkus

//

36

37 of 37

Thank you

for attending!

willem.jan.glerum@lunatech.nl

https://github.com/wjglerum