Backend Development 8 min read

Exploring Spring Boot 3.2 with Java 21, Virtual Threads, and GraalVM Native Images

This article demonstrates how to use Spring Boot 3.2 with Java 21, enabling virtual threads and GraalVM native images, providing step‑by‑step setup, configuration, and sample code for controllers, async tasks, and scheduled jobs, and discusses performance benefits and considerations.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Exploring Spring Boot 3.2 with Java 21, Virtual Threads, and GraalVM Native Images

Spring Boot 3.2 has been released and fully supports Java 21, virtual threads (Project Loom), and GraalVM native images, allowing developers to build modern, high‑performance Java applications.

Java 21 brings thousands of performance, stability, and security improvements that boost developer productivity and enable organizational innovation.

Virtual threads are a lightweight concurrency feature provided by Project Loom. For details see JEP 444. They allow the JVM to handle many concurrent tasks with a small number of OS threads.

GraalVM native images compile Java code ahead‑of‑time into a standalone executable that includes the application classes, dependencies, runtime libraries, and statically linked native code, resulting in faster startup and lower runtime memory usage compared with a traditional JVM.

To get started, install Java 21.0.1‑graal via SDKMAN:

sdk install java 21.0.1-graal
sdk default java 21.0.1-graal

Create a new Spring Boot project (e.g., with Spring Initializr) using Spring Boot 3.2.0, Java 21, Gradle‑Groovy, and add the Spring Web and GraalVM native support dependencies.

Enable virtual threads by adding the following property to application.yml or application.properties :

spring.threads.virtual.enabled=true

This makes Tomcat use virtual threads for HTTP request handling, allows @Async methods and Spring WebFlux blocking execution to run on virtual threads, and runs @Scheduled methods on virtual threads.

Example controller using virtual threads:

@RestController
@RequestMapping("/test")
public class TestController {
    private static final Logger log = LoggerFactory.getLogger(TestController.class);

    @GetMapping
    public void test() {
        log.info("Rest controller method has been called {}", Thread.currentThread());
    }
}

Async task example:

@Component
public class AsyncTaskExecutorService {
    private static final Logger log = LoggerFactory.getLogger(AsyncTaskExecutorService.class);

    @Async
    public void run() {
        log.info("Async task method has been called {}", Thread.currentThread());
    }
}

Scheduled task example (runs every 15 seconds):

@Component
public class SchedulerService {
    private static final Logger log = LoggerFactory.getLogger(SchedulerService.class);

    @Scheduled(fixedDelayString = "15000")
    public void run() {
        log.info("Scheduled method has been called {}", Thread.currentThread());
    }
}

Run the application with ./gradlew bootRun and invoke the endpoint:

curl -X GET 'localhost:8085/test'

The logs show that the controller, async, and scheduled methods are executed on virtual threads belonging to the common ForkJoinPool:

Async task method has been called VirtualThread[#52,task-1]/runnable@ForkJoinPool-1-worker-5
Scheduled method has been called VirtualThread[#46,scheduling-1]/runnable@ForkJoinPool-1-worker-1
Rest controller method has been called VirtualThread[#62,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1

According to JEP 444, the virtual‑thread scheduler is a work‑stealing ForkJoinPool that runs in FIFO order, with parallelism equal to the number of platform threads available for scheduling virtual threads.

To build a GraalVM native image, execute:

./gradlew nativeCompile
./build/native/nativeCompile/app

The native executable starts much faster and uses less memory, confirming the advantages described for native images.

Conclusion : Spring Boot 3.2 combined with virtual threads and GraalVM native images enables developers to write code that approaches Go‑level performance while retaining the rich Java ecosystem, though care must be taken to ensure libraries are compatible with virtual threads.

backend developmentSpring BootVirtual ThreadsGraalVMnative-imageJava 21
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.