Skip to main content
Version: Next

ContextAwareCompletableFuture

Why Do We Need ContextAwareCompletableFuture?

Java provides asynchronous execution support via the CompletableFuture API. However, when you execute code asynchronously using CompletableFuture.supplyAsync(...), the JVM spawns a new thread that does not inherit the context of the original (main) thread.

In the Navida Pro ecosystem, every backend request captures and stores:

  • TenantContext – Identifies which tenant the request belongs to
  • TraceId – A unique identifier to trace the full journey of a request across services

Unfortunately, when using default CompletableFuture, this critical context information is lost in the new thread. This can:

  • Break observability
  • Disrupt request-specific logic
  • Hinder audit logging and security validations

To resolve this, we introduced a custom wrapper: ContextAwareCompletableFuture, which ensures that parent thread context is propagated to the async thread.


How to Use It

Instead of using Java’s built-in CompletableFuture, simply switch to ContextAwareCompletableFuture.

Without TenantContext and TraceId

public CompletableFuture<UserProfileSchemaDTO> getLastFailedActivity(
UserProfileSchemaDTO userProfileSchemaDTO, String token, String aokId, String accept) {
return CompletableFuture.supplyAsync(
() -> healthGoalService.getLastFailedTraining(token, aokId, accept));
}

In the above code, any downstream logic trying to access TenantContext or TraceId will receive null values.


With TenantContext and TraceId

public CompletableFuture<UserProfileSchemaDTO> getLastFailedActivity(
UserProfileSchemaDTO userProfileSchemaDTO, String token, String aokId, String accept) {
return ContextAwareCompletableFuture.supplyAsync(
() -> healthGoalService.getLastFailedTraining(token, aokId, accept));
}

Here, the context is automatically propagated to the new thread, ensuring traceability and tenant-specific logic work correctly.


Internals (How It Works)

ContextAwareCompletableFuture wraps the task and:

  1. Captures the current context (like TenantContextHolder and TraceContext)
  2. Reattaches this context inside the new thread
  3. Executes your logic safely within the restored context

Best Practices

  • Always use ContextAwareCompletableFuture for async code in Navida
  • Avoid mixing regular CompletableFuture and context-aware ones
  • Validate context availability in downstream service calls using logging or assertions

Summary

To maintain consistent traceability, security, and observability across threads, we strongly recommend using ContextAwareCompletableFuture for all asynchronous processing in Navida backend services.