<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rob D</title>
    <description>The latest articles on DEV Community by Rob D (@rob_d).</description>
    <link>https://dev.to/rob_d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3398208%2Fb1c75eb9-4751-4fc5-9a55-40a1b50ecf39.png</url>
      <title>DEV Community: Rob D</title>
      <link>https://dev.to/rob_d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rob_d"/>
    <language>en</language>
    <item>
      <title>The Cost of Concurrency Interop: Trading Clean Modularity for Shared JVM Efficiency</title>
      <dc:creator>Rob D</dc:creator>
      <pubDate>Wed, 01 Oct 2025 12:41:33 +0000</pubDate>
      <link>https://dev.to/rob_d/the-cost-of-concurrency-interop-trading-clean-modularity-for-shared-jvm-efficiency-478f</link>
      <guid>https://dev.to/rob_d/the-cost-of-concurrency-interop-trading-clean-modularity-for-shared-jvm-efficiency-478f</guid>
      <description>&lt;p&gt;“I pushed the boundaries of Kotlin/Java interop in the same JVM. The result? Technically perfect, but architecturally rigid. Even with a clean stub, direct coupling across subsystems is a maintenance burden, but when the goal is to keep two APIs on different platforms in lockstep, that coupling becomes a powerful diagnostic tool.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;:&lt;/p&gt;

&lt;p&gt;This article uses a Kotlin/Java interop demo as a lens on software architecture — showing how shared runtimes blur boundaries, invite circular dependencies, and turn temporary bridges into long-term traps if left unchecked. It’s a study in deliberate coupling, and why even flawless interop carries structural cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why I Built It
&lt;/h2&gt;

&lt;p&gt;When I released JCoroutines (for Java) and ABCoroutines (for Kotlin), both libraries implemented structured concurrency: cancellation, timeouts, and lifecycle scoping, but each expressed it in their own idiomatic way.&lt;/p&gt;

&lt;p&gt;Proving that they behaved identically under load was crucial. It wasn’t enough for the APIs to look similar; they had to propagate cancellation, timing, and errors the same way across complex hierarchies.&lt;/p&gt;

&lt;p&gt;That meant building something most developers would never attempt:&lt;/p&gt;

&lt;p&gt;Running two structured-concurrency engines inside the same JVM and verifying they behave as one.&lt;/p&gt;

&lt;p&gt;The result was abcoroutines-jcoroutines-interop, which is a lightweight bridge layer that allows Java and Kotlin structured coroutines to cooperate in one runtime, sharing virtual threads, tokens, and scopes.&lt;/p&gt;

&lt;p&gt;It worked; exactly as intended.&lt;br&gt;
But it revealed a truth every system designer eventually meets:&lt;/p&gt;

&lt;p&gt;Shared efficiency comes at the cost of modular clarity.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The Bridge:
&lt;/h2&gt;

&lt;p&gt;A Custom Interface Layer&lt;br&gt;
To align Kotlin’s suspend model with Java’s explicit SuspendContext, I created a custom interface layer that translated one runtime’s entry points into the other’s expectations.&lt;/p&gt;

&lt;p&gt;At the heart of this layer were functional adapters, my minimal wrappers that allowed a Kotlin coroutine block to appear as a Java callable, or vice versa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Expose a suspend block as a Java UnaryFunction
fun &amp;lt;T&amp;gt; exposeAsJava(block: suspend () -&amp;gt; T): UnaryFunction&amp;lt;Unit, T&amp;gt; =
    UnaryFunction { runBlocking { block() } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These adapters were mechanically simple but semantically deep: they ensured parity across both systems without reflection or hidden threading.&lt;/p&gt;

&lt;p&gt;However, they were not domain APIs.&lt;br&gt;
They were coupling mechanisms; tools to align runtime semantics for validation.&lt;/p&gt;

&lt;p&gt;✅ Technically sound.&lt;br&gt;
⚠️ Architecturally non-standard.&lt;br&gt;
🎯 Intentionally built that way.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Example:
&lt;/h2&gt;

&lt;p&gt;Kotlin Authoring a Java-Style Coroutine&lt;br&gt;
One of the most instructive interop tests involved Kotlin acting in Java’s concurrency dialect, this involved writing and executing a JCoroutines-style function within Kotlin’s test harness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val javaFunction = { ctx: SuspendContext -&amp;gt;
    // Capture cancellation token for parity assertions
    tokenRef.set(ctx.cancellationToken)

    // Signal readiness as soon as entry occurs
    if (!ready.isCompleted) ready.complete(Unit)

    // Return immediately — the Java coroutine pattern
    "started"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;// Convert the Java-style block into a Kotlin suspend function&lt;br&gt;
val kotlinSuspend = ExposeAsKotlin.blocking(javaFunction)&lt;/p&gt;

&lt;p&gt;// ExposeAsKotlin only builds the wrapper — it doesn't run it&lt;br&gt;
val result = runBlocking { kotlinSuspend() }&lt;br&gt;
Here Kotlin is authoring, not just invoking, a Java coroutine.&lt;/p&gt;

&lt;p&gt;The function signature (SuspendContext) -&amp;gt; String is JCoroutines, not idiomatic Kotlin.&lt;br&gt;
ExposeAsKotlin.blocking(...) constructs a suspendable wrapper that mirrors Java semantics.&lt;br&gt;
runBlocking executes it inside a structured scope, verifying identical behavior.&lt;br&gt;
🧠 In this moment, Kotlin isn’t just interoperating with Java — it’s authoring a Java coroutine, acting within the same structured runtime contract.&lt;/p&gt;

&lt;p&gt;The code is not just language interop.&lt;br&gt;
Its copying behaviour exactly: Kotlin reproducing Java’s concurrency semantics inside its own runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Controlled Coupling and Its Consequences
&lt;/h2&gt;

&lt;p&gt;From a testing perspective, this layer was invaluable:&lt;/p&gt;

&lt;p&gt;✅ Semantic parity: cancellation, timeout, and scope propagation matched exactly.&lt;br&gt;
✅ Incremental migration: Java code could be ported piece-by-piece to Kotlin.&lt;br&gt;
✅ Deterministic equivalence: identical scenarios could be executed from either side.&lt;br&gt;
But the interop is more than a test harness, it’s also a transition mechanism.&lt;/p&gt;

&lt;p&gt;By using adapters like ExposeAsKotlin, developers can progressively port JCoroutines-based Java components into native Kotlin coroutines without lock-in or behavior drift.&lt;/p&gt;

&lt;p&gt;It acts as scaffolding during a rewrite:&lt;/p&gt;

&lt;p&gt;Each Java coroutine can be mirrored as a Kotlin suspend function.&lt;br&gt;
Existing structured tests remain valid.&lt;br&gt;
Parity is guaranteed before full migration.&lt;br&gt;
The crucial caveat:&lt;/p&gt;

&lt;p&gt;🧩 ExposeAsKotlin creates the bridge! You choose when and where to run it.*&lt;br&gt;
It’s a bridge, not a runtime.&lt;/p&gt;

&lt;p&gt;That makes it an excellent transition mechanism, it is scaffolding that enables a safe cross-language rewrite, but not something to leave in place long term.&lt;/p&gt;

&lt;p&gt;Architecturally, it remains intentionally inappropriate for production:&lt;/p&gt;

&lt;p&gt;⚠️ Collapses subsystem boundaries through shared runtime state.&lt;br&gt;
⚠️ Blurs ownership of execution context and cancellation.&lt;br&gt;
⚠️ Introduces implicit lifecycle coupling that will age poorly.&lt;br&gt;
🔁 Invites circular dependencies if both sides begin calling through each other’s adapters — turning the bridge into a feedback loop.&lt;br&gt;
These cycles compile cleanly but break modular isolation, making initialization order unpredictable and long-term refactoring costly.&lt;br&gt;
I didn’t stumble into a bad boundary! I crossed it consciously, to measure the cost and prove equivalence.&lt;/p&gt;

&lt;p&gt;This kind of coupling is brilliant for diagnosis and migration, but fragile for long-term architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When Breaking the Rules Is Worth It
&lt;/h2&gt;

&lt;p&gt;Every well-layered system hides constraints.&lt;br&gt;
Sometimes you must break them; deliberately, to understand where the real pressure lies.&lt;/p&gt;

&lt;p&gt;By letting Kotlin behave as Java, I confirmed both libraries obey the same structured-concurrency laws.&lt;br&gt;
By coupling them, I saw exactly why such coupling should not persist.&lt;/p&gt;

&lt;p&gt;⚖️ Insight often comes from temporary impurity.&lt;/p&gt;

&lt;p&gt;The challenge is discipline: recognising that a bridge is a learning device, not a foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Lessons Learned
&lt;/h2&gt;

&lt;p&gt;✅ Deliberate coupling is acceptable in research; remove it before release.&lt;br&gt;
⚙️ Custom stubs belong to validation, not to API design.&lt;br&gt;
🧩 ExposeAsKotlin is powerful for migration, but it is dangerous for integration.&lt;br&gt;
🧠 Architectural maturity means knowing when a shortcut is a scaffold.&lt;br&gt;
🔁 Avoid circular awareness. Once both languages depend on each other’s adapters, the boundary you meant to observe is gone.&lt;br&gt;
Clean architecture isn’t about perfection, it’s about intentional imperfection in service of understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Closing Reflection
&lt;/h2&gt;

&lt;p&gt;The JCoroutines ⇄ ABCoroutines Interop demonstrated that two structured-concurrency frameworks can coexist cleanly on one JVM.&lt;br&gt;
But it also shows a universal lesson:&lt;/p&gt;

&lt;p&gt;Shared efficiency costs modular independence.&lt;br&gt;
Every temporary bridge needs a demolition plan.&lt;/p&gt;

&lt;p&gt;So yes! The interop works flawlessly.&lt;br&gt;
It proves parity, supports migration, and validates design.&lt;br&gt;
But its highest purpose is to disappear once its job is done.&lt;/p&gt;

&lt;p&gt;Clean architecture isn’t about never crossing boundaries. The important cnsiderations are about knowing why you did, what you learned, and when to dismantle the bridge.&lt;/p&gt;

&lt;p&gt;And while every example here is written in Kotlin, the lesson is universal.&lt;br&gt;
The same architectural pressures exist in Java, or any language where runtime sharing tempts you to trade boundaries for convenience.&lt;br&gt;
The syntax changes; the principles don’t.&lt;/p&gt;

&lt;p&gt;📘 &lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;p&gt;🔗 JCoroutines on GitHub – Java structured concurrency on virtual threads.&lt;br&gt;
🔗 ABCoroutines – Kotlin façade for Java-style structured concurrency.&lt;br&gt;
🔗 JCoroutines-Interop – Full source and parity tests for this post.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Kotlin Virtual Threads Without the Magic: ABCoroutines for Kotlin</title>
      <dc:creator>Rob D</dc:creator>
      <pubDate>Tue, 30 Sep 2025 12:00:01 +0000</pubDate>
      <link>https://dev.to/rob_d/kotlin-virtual-threads-without-the-magic-abcoroutines-for-kotlin-4fig</link>
      <guid>https://dev.to/rob_d/kotlin-virtual-threads-without-the-magic-abcoroutines-for-kotlin-4fig</guid>
      <description>&lt;h2&gt;
  
  
  The Opportunity
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That’s why I built ABCoroutines — a small, explicit toolkit that turns JDK 21 Virtual Threads into a structured-concurrency experience for Kotlin developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The Real Problem
&lt;/h2&gt;

&lt;p&gt;Virtual Threads are threads, not coroutines — they don’t compose naturally.&lt;br&gt;
Existing frameworks hide too much behind magic or global context.&lt;br&gt;
Most examples focus on “Hello Virtual Thread”, not real coordination patterns. Memory leaks are common.&lt;br&gt;
Kotlin already has a great coroutine model, but it doesn’t automatically map onto JDK Virtual Threads. ABCoroutines bridges that gap.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚙️ Introducing ABCoroutines
&lt;/h2&gt;
&lt;h1&gt;
  
  
  🧩 Structured concurrency built on JDK 21 Virtual Threads — without the magic.
&lt;/h1&gt;

&lt;p&gt;It’s a minimal, composable toolkit offering:&lt;/p&gt;

&lt;p&gt;A VirtualThreads CoroutineDispatcher (backed by Executors.newVirtualThreadPerTaskExecutor())&lt;br&gt;
A long-lived applicationScope&lt;br&gt;
Predictable lifecycle management (ensureRunning, shutdown, reset)&lt;br&gt;
High-level coordination patterns: parallel, zip, raceForSuccess, retry&lt;br&gt;
Clean timeout and retry wrappers using Kotlin’s Duration&lt;br&gt;
Java interop through standard Executor / ExecutorService&lt;br&gt;
74 tests verifying cancellation, resource safety, and concurrency&lt;/p&gt;
&lt;h1&gt;
  
  
  🧩 Relationship to JCoroutines
&lt;/h1&gt;

&lt;p&gt;ABCoroutines builds on concepts proven in my earlier project, JCoroutines — a pure Java 21 implementation of structured concurrency designed around Virtual Threads.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;To keep things focused, this release includes only minimal interop:&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;A more complete JCoroutines ↔ ABCoroutines interop layer is currently being prepared for release.&lt;br&gt;
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.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧠 Thinking in Patterns, Not Primitives
&lt;/h2&gt;

&lt;p&gt;Instead of juggling thread pools, you express intent:&lt;/p&gt;

&lt;p&gt;Parallel Execution&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val results = parallel(
    { fetchUserProfile(userId) },
    { fetchUserOrders(userId) },
    { fetchUserPreferences(userId) }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three operations run concurrently on virtual threads. If any fails, the others are cancelled.&lt;/p&gt;

&lt;h1&gt;
  
  
  Racing for the Fastest Result
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val quote = raceForSuccess(
    { fetchQuoteFromProvider1() },
    { fetchQuoteFromProvider2() },
    { fetchQuoteFromProvider3() }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns the first successful result, cancelling the slower operations.&lt;/p&gt;

&lt;h1&gt;
  
  
  Combining Results
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val (profile, orders) = zip(
    { fetchUserProfile(userId) },
    { fetchUserOrders(userId) }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for both results, but cancel everything if either fails.&lt;/p&gt;

&lt;h1&gt;
  
  
  Retry with Exponential Backoff
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val data = retry(
    maxAttempts = 3,
    initialDelay = 100.milliseconds,
    factor = 2.0
) {
    fetchFromUnreliableApi()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically retries failed operations with configurable backoff.&lt;/p&gt;

&lt;h1&gt;
  
  
  Timeouts
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val result = withTimeout(5.seconds) {
    slowBlockingOperation()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean timeout handling with proper cancellation.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Lifecycle Management
&lt;/h2&gt;

&lt;p&gt;ABCoroutines provides explicit lifecycle control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Application startup
ABCoroutines.ensureRunning()

// Long-running background task
applicationScope.launch {
    while (isActive) {
        processQueue()
        delay(1.minutes)
    }
}

// Graceful shutdown
ABCoroutines.shutdown(timeout = 30.seconds)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hidden state, no global magic — just predictable behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 Java Interoperability
&lt;/h2&gt;

&lt;h1&gt;
  
  
  Need to integrate with Java code?
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val executor: ExecutorService = ABCoroutines.asExecutorService()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;// Pass to Java libraries expecting ExecutorService&lt;br&gt;
javaLibrary.setExecutor(executor)&lt;br&gt;
Virtual threads work seamlessly with existing Java concurrency APIs.&lt;/p&gt;

&lt;p&gt;🎯 Real-World Use Cases&lt;br&gt;
Web Server Request Handling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;applicationScope.launch(VirtualThreads) {
    val (user, permissions, settings) = parallel(
        { userRepository.findById(userId) },
        { permissionService.getPermissions(userId) },
        { settingsRepository.getSettings(userId) }
    )

    buildResponse(user, permissions, settings)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database Connection Pool Integration&lt;/p&gt;

&lt;h2&gt;
  
  
  // Run blocking JDBC calls on virtual threads
&lt;/h2&gt;

&lt;p&gt;suspend fun &amp;lt;T&amp;gt; withConnection(block: (Connection) -&amp;gt; T): T {&lt;br&gt;
    return withContext(VirtualThreads) {&lt;br&gt;
        dataSource.connection.use { conn -&amp;gt;&lt;br&gt;
            block(conn)&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
External API Integration with Fallbacks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val data = raceForSuccess(
    { primaryApi.fetch() },
    { 
        delay(500.milliseconds)
        secondaryApi.fetch() 
    }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try the primary API, but switch to secondary if it’s too slow.&lt;/p&gt;

&lt;p&gt;📊 Why Virtual Threads?&lt;br&gt;
Virtual threads shine when you have:&lt;/p&gt;

&lt;p&gt;Many concurrent blocking operations (database queries, file I/O, HTTP calls)&lt;br&gt;
Legacy blocking code that can’t easily be converted to async&lt;br&gt;
Simpler mental model than async/await for I/O-bound work&lt;br&gt;
ABCoroutines gives you virtual threads with the structured concurrency guarantees you expect from Kotlin.&lt;/p&gt;

&lt;p&gt;🚀 Getting Started&lt;br&gt;
Available on Maven Central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    implementation("tech.robd:abcoroutines:0.1.0")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requirements:&lt;/p&gt;

&lt;p&gt;Kotlin 2.0+&lt;br&gt;
Java 21 or later&lt;br&gt;
kotlinx.coroutines&lt;br&gt;
Quick Start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import tech.robd.abcoroutines.*

fun main() {
    ABCoroutines.ensureRunning()

    runBlocking {
        val result = withContext(VirtualThreads) {
            // Your blocking code here
            performBlockingOperation()
        }
        println("Result: $result")
    }

    ABCoroutines.shutdown()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎓 Design Philosophy&lt;br&gt;
ABCoroutines follows three principles:&lt;/p&gt;

&lt;p&gt;Explicit over Implicit: No hidden global state or context injection&lt;br&gt;
Composable Primitives: Build complex patterns from simple building blocks&lt;br&gt;
Fail-Safe Defaults: Proper cancellation and resource cleanup by default&lt;br&gt;
🔗 Learn More&lt;br&gt;
GitHub: github.com/robdeas/abcoroutines&lt;br&gt;
Documentation: Full examples and API docs in the README&lt;br&gt;
Maven Central: central.sonatype.com/artifact/tech.robd/abcoroutines&lt;br&gt;
💭 Final Thoughts&lt;br&gt;
Virtual Threads are powerful, but raw threads aren’t enough. ABCoroutines gives you the structure you need without sacrificing transparency.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Try ABCoroutines today and let me know what you think! Issues, suggestions, and contributions are welcome on GitHub.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Does Concurrency Have to Be So Hard in Java After 20 Years?</title>
      <dc:creator>Rob D</dc:creator>
      <pubDate>Mon, 22 Sep 2025 21:12:16 +0000</pubDate>
      <link>https://dev.to/rob_d/why-does-concurrency-have-to-be-so-hard-in-java-after-20-years-jmb</link>
      <guid>https://dev.to/rob_d/why-does-concurrency-have-to-be-so-hard-in-java-after-20-years-jmb</guid>
      <description>&lt;p&gt;Java has been around for nearly three decades, and we’ve had threads since day one. We got &lt;code&gt;java.util.concurrent&lt;/code&gt; in 2004, lambdas in 2014, CompletableFuture improvements, reactive streams, and virtual threads in 2023…&lt;/p&gt;

&lt;p&gt;And yet, writing &lt;em&gt;correct concurrent code&lt;/em&gt; in Java still feels like navigating a minefield.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why is this still so hard?
&lt;/h1&gt;




&lt;h2&gt;
  
  
  The Problem: Too Many Half-Solutions
&lt;/h2&gt;

&lt;p&gt;Let’s take a simple case:&lt;br&gt;&lt;br&gt;
Fetch data from three APIs concurrently, process results, handle errors, and respect a timeout.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Threads (1995)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;synchronizedList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;CountDownLatch&lt;/span&gt; &lt;span class="n"&gt;latch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CountDownLatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// What do we do here?&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;latch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countDown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// … repeat for t2, t3 …&lt;/span&gt;

    &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;t3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;latch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// What if it times out?&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Now what? Cancel the threads?&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Hope for the best&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Problems:&lt;/p&gt;

&lt;p&gt;Manual lifecycle management&lt;/p&gt;

&lt;p&gt;No consistent error propagation&lt;/p&gt;

&lt;p&gt;Cancellation is basically impossible&lt;/p&gt;
&lt;h3&gt;
  
  
  2. ExecutorService (2004)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newFixedThreadPool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api1"&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api2"&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api3"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeoutException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;awaitTermination&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdownNow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Fingers crossed&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Better, but still verbose and error-prone.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. CompletableFuture (2014)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;f2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api2"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;f3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api3"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenApply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;f2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exceptionally&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Cleaner, but:&lt;/p&gt;

&lt;p&gt;No structured concurrency&lt;/p&gt;

&lt;p&gt;Cancellation is awkward&lt;/p&gt;

&lt;p&gt;Error handling is ad-hoc&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Virtual Threads (2023)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api1"&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api2"&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api3"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Virtual threads help performance, but the core problems remain:&lt;/p&gt;

&lt;p&gt;No automatic cancellation&lt;/p&gt;

&lt;p&gt;No clear error boundaries&lt;/p&gt;

&lt;p&gt;Timeouts are still manual&lt;/p&gt;
&lt;h1&gt;
  
  
  What’s Wrong Here?
&lt;/h1&gt;

&lt;p&gt;After 20+ years, Java’s concurrency is still missing:&lt;/p&gt;

&lt;p&gt;Structured Concurrency — no parent/child lifetimes, leading to leaks.&lt;/p&gt;

&lt;p&gt;Reliable Cancellation — interruption is unreliable and inconsistent.&lt;/p&gt;

&lt;p&gt;Consistent Error Handling — failures don’t cleanly propagate.&lt;/p&gt;

&lt;p&gt;Resource Safety — Executors and threads must be closed manually.&lt;/p&gt;

&lt;p&gt;Context — no standard way to pass cancellation tokens, timeouts, or tracing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other languages got this right:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go has context.WithTimeout for group cancellation.&lt;/p&gt;

&lt;p&gt;Kotlin has coroutines with structured scopes.&lt;/p&gt;

&lt;p&gt;C# has Tasks with CancellationToken.&lt;/p&gt;
&lt;h1&gt;
  
  
  What We Actually Want
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try (var scope = new CoroutineScope()) {
    var results = List.of("api1", "api2", "api3").stream()
        .map(api -&amp;gt; scope.async(suspend -&amp;gt; callApi(suspend, api)))
        .map(handle -&amp;gt; handle.join())
        .toList();
    return results;
} // Automatic cleanup, cancellation, and error propagation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is:&lt;/p&gt;

&lt;p&gt;Structured — parent/child relationships are explicit&lt;/p&gt;

&lt;p&gt;Cancellable — cooperative and consistent&lt;/p&gt;

&lt;p&gt;Safe — resources cleaned up automatically&lt;/p&gt;

&lt;p&gt;Transparent — errors bubble naturally&lt;/p&gt;
&lt;h1&gt;
  
  
  Enter JCoroutines 🚀
&lt;/h1&gt;

&lt;p&gt;Concurrency should feel like an elevator: press a few clear buttons and trust the machinery.&lt;/p&gt;

&lt;p&gt;That’s what I set out to build with JCoroutines:&lt;/p&gt;

&lt;p&gt;Structured concurrency by default&lt;/p&gt;

&lt;p&gt;Explicit context passing (cancellation, timeouts, schedulers)&lt;/p&gt;

&lt;p&gt;No compiler magic — just clean Java APIs on top of virtual threads&lt;/p&gt;

&lt;p&gt;It’s small, explicit, and available today.&lt;/p&gt;

&lt;p&gt;Try It Out&lt;br&gt;
On Maven Central:&lt;/p&gt;

&lt;p&gt;maven xml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;tech.robd&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jcoroutines&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;0.1.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or Gradle:&lt;/p&gt;

&lt;p&gt;kotlin gradle.build.kts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation("tech.robd:jcoroutines:0.1.0")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Path Forward&lt;br&gt;
Java itself is heading this way (see JEP 428 on structured concurrency), but it will take years before that’s fully stable.&lt;/p&gt;

&lt;p&gt;Meanwhile, JCoroutines gives you these patterns now — using just Java 21+ and virtual threads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://central.sonatype.com/artifact/tech.robd/jcoroutines" rel="noopener noreferrer"&gt;📦 Maven Central&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/robdeas/jcoroutines" rel="noopener noreferrer"&gt;💻 GitHub repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>performance</category>
    </item>
    <item>
      <title>RoboShellGuard: Building an AI-Assisted Command Approval System for SSH Security</title>
      <dc:creator>Rob D</dc:creator>
      <pubDate>Thu, 31 Jul 2025 22:39:22 +0000</pubDate>
      <link>https://dev.to/rob_d/shellguard-building-an-ai-assisted-command-approval-system-for-ssh-security-36h3</link>
      <guid>https://dev.to/rob_d/shellguard-building-an-ai-assisted-command-approval-system-for-ssh-security-36h3</guid>
      <description>&lt;p&gt;How we built a real-time command approval system to make AI automation safer. Please note: This is a preview system and more features will be needed before deploying it to production, but its real and it works! &lt;br&gt;
**RoboShellGuard **is open source and licensed under the GNU General Public License v3.0. You can find the full license text in the project's repository. &lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem: AI Agents Gone Wild
&lt;/h2&gt;

&lt;p&gt;Picture this: You’re deploying an AI agent to help manage your infrastructure. It’s 3 AM, the agent decides to “clean up” your production database, and suddenly you’re having the worst morning of your career.&lt;/p&gt;

&lt;p&gt;Sound familiar? Not yet? Come back in a year or two if you’re not already having nightmares about it. As AI agents become more common in DevOps workflows, we’re facing a critical challenge: &lt;strong&gt;How do we harness the power of AI automation while maintaining control and safety?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional solutions like restricted shells help, but they don’t provide real-time insight, decision-making, or audit trails. That’s why we built *&lt;em&gt;ShellGuard *&lt;/em&gt;– an AI-assisted command approval and risk assessment system.&lt;/p&gt;

&lt;p&gt;Maybe AI will never be let loose near live, or even test environments, maybe you can just use it on development environments, but if your competitors do you may get little choice, but you don’t have to cut so many corners that disaster is just round one!&lt;/p&gt;
&lt;h2&gt;
  
  
  What is RoboShellGuard?
&lt;/h2&gt;

&lt;p&gt;RoboShellGuard acts as an intelligent proxy between AI agents and your SSH environments. Think of it as a “human-in-the-loop” system that analyzes commands in real-time using AI for risk assessment. It routes high-risk commands to human operators for approval and provides audit trails for every command and decision. ShellGuard integrates seamlessly with existing AI systems via its simple WebSocket API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analyzes commands in real-time&lt;/strong&gt; using intelligent risk assessment&lt;br&gt;
&lt;strong&gt;Routes high-risk commands&lt;/strong&gt; to human operators for approval&lt;br&gt;
&lt;strong&gt;Provides full audit trails&lt;/strong&gt; of every command and decision&lt;br&gt;
&lt;strong&gt;Integrates seamlessly&lt;/strong&gt; with existing AI systems via WebSocket API&lt;br&gt;
Architecture Overview&lt;br&gt;
RoboShellGuard is built with &lt;strong&gt;Kotlin&lt;/strong&gt; and &lt;strong&gt;Spring Boot&lt;/strong&gt;, designed for reliability and enterprise deployment. Here’s how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent → WebSocket API → Risk Assessor → Approval Engine → SSH Execution
     ↓                           ↓              ↓              ↓
   Command              Risk Score      Human Review     Audit Log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Core Components
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WebSocket JSON API&lt;/strong&gt; – Real-time communication with AI agents&lt;br&gt;
&lt;strong&gt;Risk Assessment Engine&lt;/strong&gt; – Sophisticated command analysis&lt;br&gt;
&lt;strong&gt;Approval Center&lt;/strong&gt; – Web dashboard for human oversight&lt;br&gt;
&lt;strong&gt;Terminal Test Client&lt;/strong&gt; – Development and testing interface&lt;br&gt;
&lt;strong&gt;Audit System&lt;/strong&gt; – Complete command history and compliance&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started: The Developer Experience
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Setting Up ShellGuard
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone the repository
git clone https://github.com/robokeys/shellguard.git
cd shellguard

# Build with Gradle
./gradlew build

# Start the server
./gradlew bootRun

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Connecting Your AI Agent
&lt;/h3&gt;

&lt;p&gt;The WebSocket API makes integration straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Connect to ShellGuard
const ws = new WebSocket('ws://localhost:8080/rkcl/ws');

// Send a command for approval
const command = {
    "command": "LINE"
    "parameter": systemctl restart nginx",
    "session": "production"
};

ws.send(JSON.stringify(command));

// Handle the response
ws.onmessage = (event) =&amp;gt; {
    const response = JSON.parse(event.data);

    switch(response.status) {
        case 'PENDING':
            console.log('Command submitted for approval');
            break;
        case 'APPROVED':
            console.log('Command approved:', response.result);
            break;
        case 'REJECTED':
            console.log('Command rejected:', response.reason);
            break;
    }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Testing with the Terminal Client
RoboShellGuard includes a built-in test client that uses the same WebSocket interface as production AI agents:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Real-time command testing&lt;br&gt;
WebSocket connection management&lt;br&gt;
Response monitoring&lt;br&gt;
Session handling&lt;/strong&gt;&lt;br&gt;
This ensures your integration testing is as close to production as possible.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;The power of RoboShellGuard lies in its simple, yet robust, protocol-based interface. While we’ll be releasing dedicated clients for specific AI systems in the future, the core of ShellGuard is a simple WebSocket API. This approach provides maximum flexibility, allowing you to integrate with any agent or language you choose.&lt;/p&gt;

&lt;p&gt;The RoboShellGuard protocol supports various prefixes to handle different types of input. For example, command strings are sent with a LINE: prefix, while interactive keystrokes, such as an up arrow, use a KEY: prefix. This design gives you a clear and consistent way to send different types of shell input, receive real-time risk assessments, and manage the approval workflow. Commands may also be sent in JSON format.&lt;/p&gt;

&lt;p&gt;Below are a few examples demonstrating how an AI agent could use RoboShellGuard in different scenarios.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Infrastructure Management AI
&lt;/h3&gt;
&lt;h1&gt;
  
  
  Example: AI agent managing server deployments
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class InfrastructureAgent:
    def __init__(self):
        self.shellguard = ShellGuardClient()

    def deploy_service(self, service_name):
        # High-risk command - will require approval
        result = self.shellguard.execute(
            f"LINE:sudo systemctl stop {service_name}"
        )

        if result.approved:
            # Continue with deployment
            self.shellguard.execute(
                f"LINE:docker pull {service_name}:latest"
            )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Database Maintenance Automation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Example: Automated database cleanup
const maintenanceCommands = [
    "LINE:pg_dump production_db &amp;gt; backup.sql",  // Low risk - auto-approved
    "LINE:DROP TABLE temp_analytics;",          // High risk - needs approval
    "LINE:VACUUM ANALYZE;"                      // Medium risk - configurable
];

for (const cmd of maintenanceCommands) {
    await shellguard.executeWithApproval(cmd);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Security Response Automation
&lt;/h3&gt;

&lt;p&gt;For commands that require more context, such as a security response, the API can accept a detailed JSON object.&lt;/p&gt;

&lt;p&gt;// Example: Automated incident response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SecurityResponseAgent {
    fun handleSuspiciousActivity(ipAddress: String) {
        // Block suspicious IP - high risk command
        shellGuard.submitCommand(
            command = "LINE”
            parameter = ”iptables -A INPUT -s $ipAddress -j DROP",
            priority = CommandPriority.HIGH,
            reason = "Blocking suspicious IP: $ipAddress"
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Risk Assessment Engine
&lt;/h2&gt;

&lt;p&gt;One of RoboShellGuard’s key features is intelligent risk scoring. Commands can be analyzed based on:&lt;/p&gt;

&lt;h3&gt;
  
  
  Risk Factors
&lt;/h3&gt;

&lt;p&gt;Privilege level (sudo, root access)&lt;br&gt;
Destructive potential (rm, drop, delete operations)&lt;br&gt;
System impact (service restarts, network changes)&lt;br&gt;
Data sensitivity (database operations, file modifications)&lt;/p&gt;
&lt;h3&gt;
  
  
  Configurable Rules
&lt;/h3&gt;

&lt;p&gt;Coming soon !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example risk assessment configuration
risk_assessment:
  rules:
    - pattern: "sudo rm -rf .*"
      risk_level: CRITICAL
      require_approval: true

    - pattern: "systemctl restart .*"
      risk_level: MEDIUM
      require_approval: false
      notify_operators: true

    - pattern: "ls .*|ps .*|cat .*"
      risk_level: LOW
      auto_approve: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building for Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security Considerations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: Integrate with your existing auth systems  (Coming soon)&lt;br&gt;
&lt;strong&gt;Authorization&lt;/strong&gt;: Role-based approval workflows  (Coming soon)&lt;br&gt;
&lt;strong&gt;Encryption&lt;/strong&gt;: TLS for all communications  (Coming soon)&lt;br&gt;
&lt;strong&gt;Audit Logging&lt;/strong&gt;: Immutable command history&lt;/p&gt;
&lt;h3&gt;
  
  
  Scalability Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Horizontal scaling&lt;/strong&gt; with Spring Boot&lt;br&gt;
&lt;strong&gt;Database persistence&lt;/strong&gt; for audit trails (comming soon)&lt;br&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; for high-availability&lt;br&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; with built-in metrics&lt;/p&gt;
&lt;h3&gt;
  
  
  Integration Points
&lt;/h3&gt;

&lt;p&gt;ShellGuard plays well with existing tools:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD pipelines&lt;/strong&gt; (Jenkins, GitLab CI, GitHub Actions)&lt;br&gt;
&lt;strong&gt;Monitoring systems&lt;/strong&gt; (Prometheus, Grafana)&lt;br&gt;
&lt;strong&gt;Chat ops&lt;/strong&gt; (Slack, Microsoft Teams)&lt;br&gt;
&lt;strong&gt;SIEM platforms&lt;/strong&gt; (Splunk, ELK Stack)&lt;/p&gt;
&lt;h2&gt;
  
  
  The HTMX-Powered Dashboard
&lt;/h2&gt;

&lt;p&gt;The approval center uses HTMX for real-time updates without heavy JavaScript frameworks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Live updating approval queue --&amp;gt;
&amp;lt;div hx-get="/api/pending-approvals" 
     hx-trigger="every 2s"
     hx-target="#approval-queue"&amp;gt;
    &amp;lt;!-- Approval cards inserted here --&amp;gt;
&amp;lt;/div&amp;gt;
This approach gives us:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Instant updates&lt;/strong&gt; as commands arrive&lt;br&gt;
&lt;strong&gt;Minimal complexity&lt;/strong&gt; compared to React/Vue&lt;br&gt;
&lt;strong&gt;Better performance&lt;/strong&gt; for real-time dashboards&lt;br&gt;
&lt;strong&gt;Progressive enhancement&lt;/strong&gt; that works without JavaScript&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;We’re actively developing ShellGuard with planned features including:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine learning&lt;/strong&gt; risk models&lt;br&gt;
&lt;strong&gt;Integration plugins&lt;/strong&gt; for major platforms&lt;br&gt;
&lt;strong&gt;Advanced reporting **and analytics&lt;br&gt;
**Multi-tenant&lt;/strong&gt; support for managed services&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;RoboShellGuard is open source and ready for production use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; github.com/robokeys/Roboshellguard&lt;br&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; Full user guide PDF &lt;a href="https://robokeys.tech/assets/About%20Robokeys%20ShellGuard.pdf" rel="noopener noreferrer"&gt;https://robokeys.tech/assets/About%20Robokeys%20ShellGuard.pdf&lt;/a&gt; &lt;br&gt;
&lt;strong&gt;ShellGuard Product Page:&lt;/strong&gt; &lt;a href="https://robokeys.tech/shellguard.html" rel="noopener noreferrer"&gt;https://robokeys.tech/shellguard.html&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; Coming soon!&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RoboShellGuard&lt;/strong&gt; is open source and licensed under the GNU General Public License v3.0. You can find the full license text in the project's repository.&lt;br&gt;
We welcome contributions! Areas where we’d love help:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk assessment algorithms&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Integration plugins **(Terraform, Ansible, etc.)&lt;br&gt;
**UI/UX improvements&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Documentation and examples&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI automation is inevitable, but it doesn’t have to be uncontrolled. RoboShellGuard provides the safety net that lets you move fast without breaking things.&lt;/p&gt;

&lt;p&gt;The future of DevOps isn’t choosing between human oversight and AI efficiency – it’s combining both intelligently. RoboShellGuard makes that possible today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to learn more?&lt;/strong&gt; Check out our other tools in the Robokeys ecosystem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RoboKeyTags:&lt;/strong&gt; Semantic metadata for AI-assisted development&lt;br&gt;
&lt;strong&gt;Verzanctuary:&lt;/strong&gt; Safe experimentation without polluting Git history&lt;br&gt;
&lt;strong&gt;More projects:&lt;/strong&gt; &lt;a href="https://robokeys.tech" rel="noopener noreferrer"&gt;https://robokeys.tech&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What are your thoughts on AI automation safety? Have you faced similar challenges? Let’s discuss in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devops</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
