Asynchronous Mode and Java 25

Async Configuration is defined in AsyncConfiguration.kt.

Threading Model

Embabel uses a dedicated executor for all agent operations with an inheritance-based threading model.

Configuration Properties

Two orthogonal properties control Embabel’s threading behavior:

  1. Threading Model Inheritance (embabel.agent.platform.threading.override)
    • false (default): Inherit threading model from spring.threads.virtual.enabled
    • true: Flip the threading model (virtual→platform or platform→virtual)
  2. Executor Sharing (embabel.agent.platform.threading.shared)
    • false (default): Create isolated executor for Embabel operations
    • true: Share app’s applicationTaskExecutor bean when threading models match

Configuration in agent-platform.properties:

# Flip inherited threading model (virtual-to-platform, platform-to-virtual)
embabel.agent.platform.threading.override=false

# Share app's executor when both use the same threading model
embabel.agent.platform.threading.shared=false

Behavior Matrix

App ThreadsOverrideSharedEmbabel Threading ModelExecutor Type
PlatformfalsefalsePlatform (inherit)Isolated platform executor (default)
PlatformfalsetruePlatform (inherit)Shared app’s platform executor
PlatformtruefalseVirtual (flip)Isolated virtual executor
PlatformtruetrueVirtual (flip)Isolated virtual executor (models differ)
VirtualfalsefalseVirtual (inherit)Isolated virtual executor
VirtualfalsetrueVirtual (inherit)Shared app’s virtual executor
VirtualtruefalsePlatform (flip)Isolated platform executor
VirtualtruetruePlatform (flip)Isolated platform executor (models differ)

Key insight*: shared=true enables executor sharing when both app and Embabel use the same threading model* (either platform/platform or virtual/virtual). When threading models differ, Embabel always creates an isolated executor.

Note: Virtual threads require Java 21+. On Java 17-20, Embabel automatically falls back to platform threads even if virtual threads are requested.

Framework employs Asyncer abstraction consistently across Agent Invocation (see ), Agent Actions, Tool Loop, ShellCommands, and other scenarios.

Java 25 Implications

Prior to Java 25, the JVM was imprecise when reading container CPU limits — it often saw the host’s full CPU count rather than the container’s cgroup allocation. Java 25 corrects this to accurately respect cgroup CPU limits (e.g., Kubernetes resources.limits.cpu).

Related OpenJDK ticket: JDK-8362881

When running Embabel on Java 25 inside containers (Docker/Kubernetes), all agent actions execute serially instead of in parallel. As mentioned above this is caused by Java 25’s improved (more accurate) cgroup CPU detection, which reports availableProcessors() = 1 in CPU-constrained containers. Since ForkJoinPool.commonPool sizes its parallelism based on this value, the effective parallelism drops to 1, serializing all concurrent agent operations.

JVM VersionavailableProcessors()Common pool parallelismBehavior
Pre-25Host CPU count (e.g., 8)7Actions run in parallel
25Container CPU limit (e.g., 1)1 (minimum)Actions serialize

Also, note: modern (post 1.3) Kotlin coroutines implementation in Dispatchers.Default and Dispatchers.IO is backed by Kotlin’s own coroutine scheduler, not Java’s ForkJoinPool.commonPool().

Why Embabel is Safe

  1. Asyncer Abstraction — All parallel execution routes through ExecutorAsyncer
  2. Dedicated Executor — Uses a dedicated executor (virtual or unbounded platform threads), not ForkJoinPool
  3. Unbounded Execution — Configured as unbounded to support parallel action execution
  4. Coroutine Defaults — Production code uses Dispatchers.IO, not Dispatchers.Default

If Users report serialization issues on Java 25, recommendation is to check for:

  1. Custom code using CompletableFuture.supplyAsync() without explicit executor
  2. Custom code using Dispatchers.Default (Kotlin coroutines)
  3. Spring misconfiguration — applicationTaskExecutor not properly configured
  4. Third-party libraries that depend on ForkJoinPool.commonPool

Workarounds (if needed)

Even though Embabel core is safe, users can apply below settings if they have custom code affected:

Option 1: Set container CPU limits

# Kubernetes
resources:
limits:
cpu: "4"
requests:
cpu: "2"

Option 2: Override ForkJoinPool parallelism

java -Djava.util.concurrent.ForkJoinPool.common.parallelism=4 -jar agent.jar

Was this page helpful?

Share