Şuayb's BlogŞuayb's Blog
Home
Categories
Games
MediumAboutContact
Language
Theme
    1. Blog
    2. Programming
    3. Spring Boot Async Tasks with Virtual Thread

Spring Boot Async Tasks with Virtual Thread

PublishedApril 26, 2025
UpdatedApril 27, 2025
Reading time3 min read
JavaKotlinSpring BootAsync TasksVirtual Thread
XLinkedInFacebook
Spring Boot Async Tasks with Virtual Thread

Loading likes...

Spring Boot 3.2+ lets you combine the simplicity of @Async with JDK 21 virtual threads for ultra-lightweight concurrency. Offload work to isolated virtual threads without complex pool configs.


Last updatedApril 27, 2025

Total viewsLoading hits...

Previous articleSpring Boot AI IntegrationNext articleSpring Boot HTTP Interface Client
Şuayb Şimşek

Written by

Şuayb Şimşek

Backend-focused fullstack developer sharing practical notes on Spring Boot, security, microservices, and cloud-native architecture.

Expertise

  • Spring Boot
  • Go
  • Microservices
  • Next.js
  • Cloud Native

Connect

GitHubLinkedInMedium

Related posts

Spring Boot Async Tasks
Programming

Spring Boot Async Tasks

Learn how to use @Async in Spring Boot to execute tasks asynchronously without blocking the main thread.

February 20, 20253 min read
JavaKotlinSpring BootAsync Tasks
Spring Boot Configuration Properties
Programming

Spring Boot Configuration Properties

Learn how to use @ConfigurationProperties for type-safe configuration, validate settings with @Validated, and manage environment-specific values with profile-specific application-{profile}.yml files.

February 4, 20263 min read
JavaKotlinSpring BootConfiguration
Spring Boot GraphQL JWE Authentication
Programming

Spring Boot GraphQL JWE Authentication

Learn how to secure your Spring Boot GraphQL APIs with stateless encrypted JWTs (JWE) while persisting user identities and roles in a JPA-backed database.

May 17, 20256 min read
JavaKotlinSpring BootSecurityJWTJWEGraphQL

About

Articles on Spring Boot, microservices, security, and more.

ContactStart here

Latest posts

  • Captain Tsubasa 2: World Fighters
  • Captain Tsubasa: Rise of New Champions
  • Spring Boot Configuration Properties
  • Spring Boot GraphQL JWE Authentication
  • Spring Boot JWE Authentication with JPA

Top topics

JavaKotlinSpring BootJWEJWTMicroservice

Subscribe

Get practical backend + fullstack notes when new articles are published.

Social

© 2024-2026 Şuayb's Blog. All rights reserved.

🌟 Why Use Virtual Thread in Spring Boot?

In this section, we clarify Why Use Virtual Thread in Spring Boot? and summarize the key points you will apply in implementation.

  • Ultra-Lightweight: Virtual threads are thousands of times cheaper than platform threads.
  • Non-Blocking: @Async methods run off the main thread, improving responsiveness.
  • Scalable: Handle high concurrency with minimal resource overhead.
  • Simple Config: Enable with a single property, no custom executors needed.

📋 Prerequisites

In this section, we clarify Prerequisites and summarize the key points you will apply in implementation.

  • ☕ Java Development Kit (JDK) 21 or higher
  • 📦 Spring Boot 3.2+
  • 🔤 IDE (IntelliJ IDEA, Eclipse)

🛠️ Step 1: Add Dependencies

To enable async processing, include spring-boot-starter-web in your pom.xml or build.gradle file.

Maven:

XMLpom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Gradle:

GROOVYbuild.gradle
implementation 'org.springframework.boot:spring-boot-starter-web'

🛠️ Step 2: Enable Virtual Threads

Add to application.yml or application.properties:

YAMLapplication.yml
spring:
  threads:
    virtual:
      enabled: true
PROPERTIESapplication.properties
spring.threads.virtual.enabled=true

This setting auto-configures the following:

  • applicationTaskExecutor for @Async support
  • Task scheduler for @Scheduled methods
  • Servlet container thread pools (Tomcat/Jetty) to use virtual threads

🛠️ Step 3: Enable Async Support

Annotate your main application class in Java or Kotlin:


🛠️ Step 4: Define an Async Service

Create a service with @Async. It will run each call on a new virtual thread.


🛠️ Step 5: Trigger via REST Controller

Expose endpoints to invoke your async methods:


▶️ Run the App

BASH
./mvnw spring-boot:run
# or
gradle bootRun

🧪 Test Endpoints

Trigger void task

BASH
curl http://localhost:8080/async/run

Check logs for virtual thread start/end.

Trigger task with return

BASH
curl http://localhost:8080/async/run-return
# returns "Completed"

🏁 Conclusion

You now have a practical Spring Boot Async Tasks with Virtual Thread implementation with a clear, production-friendly Spring Boot structure. As a next step, adapt configuration and tests to your own domain, then validate behavior under realistic traffic and failure scenarios.

JAVAAsyncVirtualApplication.java
package com.example.async;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncVirtualApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncVirtualApplication.class, args);
    }
}
KOTLINAsyncVirtualApplication.kt
package com.example.async

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableAsync

@SpringBootApplication
@EnableAsync
class AsyncVirtualApplication

fun main(args: Array<String>) {
    runApplication<AsyncVirtualApplication>(*args)
}
JAVAAsyncVirtualService.java
package com.example.async;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalTime;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Service
public class AsyncVirtualService {

  @Async
  public void runTask() {
    log.info("[{}] Async start on {}", LocalTime.now(), Thread.currentThread());
    try {
      Thread.sleep(1000);
    } catch (InterruptedException ignored) {}
    log.info("[{}] Async end on {}", LocalTime.now(), Thread.currentThread());
  }

  @Async
  public CompletableFuture<String> runAndReturn() throws InterruptedException {
    Thread.sleep(500);
    return CompletableFuture.completedFuture("Completed");
  }
}
KOTLINAsyncVirtualService.kt
package com.example.async

import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service

import java.time.LocalTime
import java.util.concurrent.CompletableFuture

@Service
class AsyncVirtualService {
  private val log = LoggerFactory.getLogger(AsyncVirtualService::class.java)

  @Async
  fun runTask() {
    log.info("[{}] Async start on {}", LocalTime.now(), Thread.currentThread())
    try {
      Thread.sleep(1000)
    } catch (_: InterruptedException) {}
    log.info("[{}] Async end on {}", LocalTime.now(), Thread.currentThread())
  }

  @Async
  fun runAndReturn(): CompletableFuture<String> {
    Thread.sleep(500)
    return CompletableFuture.completedFuture("Completed")
  }
}
JAVAAsyncVirtualController.java
package com.example.async;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/async")
@RequiredArgsConstructor
public class AsyncVirtualController {

  private final AsyncVirtualService service;

  @GetMapping("/run")
  public String triggerRun() {
    service.runTask();
    return "Async virtual thread task triggered";
  }

  @GetMapping("/run-return")
  public String triggerRunAndReturn() throws Exception {
    var future = service.runAndReturn();
    return future.get();
  }
}
KOTLINAsyncVirtualController.kt
package com.example.async

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

import lombok.RequiredArgsConstructor

@RestController
@RequestMapping("/async")
@RequiredArgsConstructor
class AsyncVirtualController(private val service: AsyncVirtualService) {

  @GetMapping("/run")
  fun triggerRun(): String {
    service.runTask()
    return "Async virtual thread task triggered"
  }

  @GetMapping("/run-return")
  @Throws(Exception::class)
  fun triggerRunAndReturn(): String {
    val future = service.runAndReturn()
    return future.get()
  }
}