The Problem Opportunity
Virtual Threads arrived with JDK 21 — promising lightweight concurrency without frameworks. But while they simplify threading, they don’t give you structure. You still have to manage scopes, cancellations, timeouts, and races yourself.
That’s why I built ABCoroutines — a small, explicit toolkit that turns JDK 21 Virtual Threads into a structured-concurrency experience for Kotlin developers.
💡 The Real Problem
- Virtual Threads are threads, not coroutines — they don’t compose naturally.
- Existing frameworks hide too much behind magic or global context.
- Most examples focus on “Hello Virtual Thread”, not real coordination patterns. Memory leaks are common.
Kotlin already has a great coroutine model, but it doesn’t automatically map onto JDK Virtual Threads. ABCoroutines bridges that gap.
⚙️ Introducing ABCoroutines
🧩 Structured concurrency built on JDK 21 Virtual Threads — without the magic.
It’s a minimal, composable toolkit offering:
- A VirtualThreads
CoroutineDispatcher
(backed byExecutors.newVirtualThreadPerTaskExecutor()
) - A long-lived applicationScope
- Predictable lifecycle management (
ensureRunning
,shutdown
,reset
) - High-level coordination patterns:
parallel
,zip
,raceForSuccess
,retry
- Clean timeout and retry wrappers using Kotlin’s
Duration
- Java interop through standard
Executor
/ExecutorService
- 74 tests verifying cancellation, resource safety, and concurrency
🧩 Relationship to JCoroutines
ABCoroutines builds on concepts proven in my earlier project, JCoroutines — a pure Java 21 implementation of structured concurrency designed around Virtual Threads.
While JCoroutines brings coroutine-like structure to Java itself, ABCoroutines adapts those same principles to Kotlin — combining idiomatic suspend functions with the predictability and lifecycle control of Virtual Threads.
To keep things focused, this release includes only minimal interop:
it exposes the underlying Virtual Thread Executor through a standard Executor
and ExecutorService
, so Java code can safely schedule work into the same structured environment.
A more complete JCoroutines ↔ ABCoroutines interop layer is currently being prepared for release.
It’s a fascinating area — particularly for testing and mixed Java/Kotlin projects, where being able to share structured scopes and cancellation semantics across both languages opens up new possibilities for gradual migration and hybrid systems.
🧠 Thinking in Patterns, Not Primitives
Instead of juggling thread pools, you express intent:
Parallel Execution
val results = parallel(
{ fetchUserProfile(userId) },
{ fetchUserOrders(userId) },
{ fetchUserPreferences(userId) }
)
All three operations run concurrently on virtual threads. If any fails, the others are cancelled.
Racing for the Fastest Result
val quote = raceForSuccess(
{ fetchQuoteFromProvider1() },
{ fetchQuoteFromProvider2() },
{ fetchQuoteFromProvider3() }
)
Returns the first successful result, cancelling the slower operations.
Combining Results
val (profile, orders) = zip(
{ fetchUserProfile(userId) },
{ fetchUserOrders(userId) }
)
Wait for both results, but cancel everything if either fails.
Retry with Exponential Backoff
val data = retry(
maxAttempts = 3,
initialDelay = 100.milliseconds,
factor = 2.0
) {
fetchFromUnreliableApi()
}
Automatically retries failed operations with configurable backoff.
Timeouts
val result = withTimeout(5.seconds) {
slowBlockingOperation()
}
Clean timeout handling with proper cancellation.
🏗️ Lifecycle Management
ABCoroutines provides explicit lifecycle control:
// Application startup
ABCoroutines.ensureRunning()
// Long-running background task
applicationScope.launch {
while (isActive) {
processQueue()
delay(1.minutes)
}
}
// Graceful shutdown
ABCoroutines.shutdown(timeout = 30.seconds)
No hidden state, no global magic — just predictable behavior.
🔄 Java Interoperability
Need to integrate with Java code?
val executor: ExecutorService = ABCoroutines.asExecutorService()
// Pass to Java libraries expecting ExecutorService
javaLibrary.setExecutor(executor)
Virtual threads work seamlessly with existing Java concurrency APIs.
🎯 Real-World Use Cases
Web Server Request Handling
applicationScope.launch(VirtualThreads) {
val (user, permissions, settings) = parallel(
{ userRepository.findById(userId) },
{ permissionService.getPermissions(userId) },
{ settingsRepository.getSettings(userId) }
)
buildResponse(user, permissions, settings)
}
Database Connection Pool Integration
// Run blocking JDBC calls on virtual threads
suspend fun <T> withConnection(block: (Connection) -> T): T {
return withContext(VirtualThreads) {
dataSource.connection.use { conn ->
block(conn)
}
}
}
External API Integration with Fallbacks
val data = raceForSuccess(
{ primaryApi.fetch() },
{
delay(500.milliseconds)
secondaryApi.fetch()
}
)
Try the primary API, but switch to secondary if it’s too slow.
📊 Why Virtual Threads?
Virtual threads shine when you have:
- Many concurrent blocking operations (database queries, file I/O, HTTP calls)
- Legacy blocking code that can’t easily be converted to async
- Simpler mental model than async/await for I/O-bound work
ABCoroutines gives you virtual threads with the structured concurrency guarantees you expect from Kotlin.
🚀 Getting Started
Available on Maven Central:
dependencies {
implementation("tech.robd:abcoroutines:0.1.0")
}
Requirements:
- Kotlin 2.0+
- Java 21 or later
- kotlinx.coroutines
Quick Start:
import tech.robd.abcoroutines.*
fun main() {
ABCoroutines.ensureRunning()
runBlocking {
val result = withContext(VirtualThreads) {
// Your blocking code here
performBlockingOperation()
}
println("Result: $result")
}
ABCoroutines.shutdown()
}
🎓 Design Philosophy
ABCoroutines follows three principles:
- Explicit over Implicit: No hidden global state or context injection
- Composable Primitives: Build complex patterns from simple building blocks
- Fail-Safe Defaults: Proper cancellation and resource cleanup by default
🔗 Learn More
- GitHub: github.com/robdeas/abcoroutines
- Documentation: Full examples and API docs in the README
- Maven Central: central.sonatype.com/artifact/tech.robd/abcoroutines
💭 Final Thoughts
Virtual Threads are powerful, but raw threads aren’t enough. ABCoroutines gives you the structure you need without sacrificing transparency.
If you’re building JVM server applications with blocking I/O, give it a try. It’s designed to be small, clear, and composable — no magic required.
Try ABCoroutines today and let me know what you think! Issues, suggestions, and contributions are welcome on GitHub.