Async Programming

BoxLang Futures, Executors, Async/Parallel Programming and much more.

We've already covered basic threading in our core syntax and semantics section. In this section, we will discover the power behind the asynchronous framework behind Boxlang which is powered by the JDKs CompletableFutures, Executors, and much more.

Note: This section of our documentation covers features coming soon to BoxLang 1.4.0.

BoxLang Async Framework

BoxLang's async framework provides a comprehensive suite of tools for modern concurrent programming, built on top of Java's proven concurrency primitives but enhanced with dynamic language features and developer-friendly APIs.

Async Service

The AsyncService in BoxLang is in charge of coordinating executors, schedulers and configuration for the runtime. Any executor you use via our BIFs or internal facitlities will end up being managed by this service.

Executors

All of our tasks and computing futures execute in the server's common ForkJoin pool the JDK provides. However, JDK 8+ provides you a framework for simplifying the execution of asynchronous tasks. It can automatically provide you with a pool of threads and a simple API for assigning tasks or work loads to them.

Scheduler Service

Our scheduler service is in charge of creating and managing all BoxLang schedulers, whether they are global, dynamic or from contributed modules.

Schedulers

Schedulers can be written in BoxLang or in Java and will end up being managed by the scheduler service. Each scheduler has a collection of scheduled tasks it can monitor, execute and manage. Each scheduler is bound to a specific executor.

Scheduled Tasks

Schedule tasks execute in an executor of choice and will be most likely managed by a scheduler. There are times where tasks can be sent for execution directly to executors as well.

BoxFuture

Our BoxFuture is a subclass of the JDKs CompletableFuture but enhanced for dynamic programming.

🚀 What You Can Build

With BoxLang's async framework, you can create:

High-Performance Applications

  • Non-blocking I/O operations that scale to thousands of concurrent requests

  • CPU-intensive computations distributed across multiple cores

  • Real-time data processing pipelines with backpressure handling

  • Microservices with async inter-service communication

Robust Background Processing

  • Scheduled tasks with cron-like flexibility and fluent configuration

  • Event-driven workflows that respond to system changes

  • Batch processing jobs that can be paused, resumed, and monitored

  • Queue-based processing with automatic retry and error handling

Advanced Coordination Patterns

  • Pipeline architectures for streaming data transformation

  • Fan-out/Fan-in patterns for parallel processing and aggregation

  • Circuit breakers for fault-tolerant service integration

  • Rate limiting and throttling for resource protection

🛠️ Core Capabilities

⚡ BoxFutures - Promise-Like Programming

Transform callback hell into readable, chainable operations:

// Sequential async operations
userProfile = futureNew( () => authenticateUser( credentials ) )
    .then( token => fetchUserProfile( token ) )
    .then( profile => enrichWithPermissions( profile ) )
    .then( enrichedProfile => cacheProfile( enrichedProfile ) )
    .onError( ex => handleAuthError( ex ) )
    .orTimeout( 30, "SECONDS" )

🔄 Parallel Computing

Execute operations concurrently with automatic result aggregation:

// Process multiple data sources in parallel
results = asyncAll([
    () => fetchFromDatabase(),
    () => fetchFromAPI(),
    () => fetchFromCache()
]).then( dataSources => mergeAndProcess( dataSources ) )

// Transform collections in parallel
processedUsers = asyncAllApply(
    userIds,
    userId => enhanceUserProfile( userId )
)

⏰ Intelligent Scheduling

Create sophisticated scheduling patterns with minimal code:

// Complex scheduling with conditions
scheduler.task( "data-sync" )
    .call( () => syncExternalData() )
    .every( 15, "MINUTES" )
    .when( () => isBusinessHours() )
    .onError( ex => notifyOpsTeam( ex ) )

🎛️ Flexible Executors

Choose the right execution strategy for your workload:

// I/O intensive tasks - unlimited virtual threads
ioExecutor = executorGet( "io-tasks" )  // Virtual threads for massive concurrency

// CPU intensive tasks - controlled thread pool
cpuExecutor = executorGet( "cpu-tasks" )  // Fixed pool for CPU-bound work

// Custom executors for specific needs
customExecutor = executorNew( "image-processing", "work-stealing", 8 )

🎯 Real-World Use Cases

API Gateway Pattern

// Orchestrate multiple backend services
response = asyncAll([
    () => userService.getProfile( userId ),
    () => orderService.getOrders( userId ),
    () => recommendationService.getRecommendations( userId )
])
.then( results => aggregateApiResponse( results ) )
.orTimeout( 5, "SECONDS" )
.onError( ex => fallbackResponse() )

Data Pipeline Processing

// ETL pipeline with error recovery
pipeline = futureNew( () => extractFromSource() )
    .then( data => validateData( data ), "cpu-tasks" )
    .then( validData => transformData( validData ), "cpu-tasks" )
    .then( transformedData => loadToDestination( transformedData ), "io-tasks" )
    .onError( ex => handlePipelineFailure( ex ) )

Background Job Processing

// Resilient background job with monitoring
scheduler.task( "report-generation" )
    .call( () => generateMonthlyReports() )
    .delay( 5, "MINUTES" )  // Allow system to stabilize
    .every( 1, "DAYS" )
    .at( "02:00" )  // Run at 2 AM
    .onSuccess( result => notifyCompletion( result ) )
    .onError( ex => escalateFailure( ex ) )

Race Conditions and Fallbacks

// Multiple data sources with automatic fallback
fastestData = asyncAny([
    () => primaryDatabase.query( sql ),
    () => readOnlyReplica.query( sql ),
    () => cache.get( cacheKey )
])
.then( data => processData( data ) )
.onError( ex => useDefaultData() )

🔧 Advanced Features

Resource Management

  • Automatic cleanup of threads and resources

  • Graceful shutdown with configurable timeouts

  • Memory-efficient virtual threads for I/O operations

  • CPU-aware thread pools for compute-intensive tasks

Error Handling & Resilience

  • Centralized exception management with typed error handling

  • Automatic retry logic with exponential backoff

  • Circuit breaker patterns for external service protection

  • Timeout management at multiple levels (operation, pipeline, system)

Monitoring & Observability

  • Real-time metrics for executor performance

  • Task execution statistics with completion rates

  • Async logging for debugging and monitoring

  • Health checks for scheduler and executor states

Integration & Interoperability

  • Java interop with existing concurrent libraries

  • Module system for extending async capabilities

  • Configuration-driven executor and scheduler setup

  • Hot-swappable task definitions and schedules

⏱️ Time Units Reference

BoxLang's async framework accepts time units in multiple formats for timeouts, delays, and scheduling operations. This applies to all async operations including BoxFutures, parallel computations, scheduled tasks, and executors.

Available Time Units

Unit String
Java TimeUnit
Description
Example Usage

"NANOSECONDS"

TimeUnit.NANOSECONDS

Nanosecond precision

future.orTimeout( 500000000, "NANOSECONDS" )

"MICROSECONDS"

TimeUnit.MICROSECONDS

Microsecond precision

future.orTimeout( 500000, "MICROSECONDS" )

"MILLISECONDS"

TimeUnit.MILLISECONDS

Millisecond precision (default)

future.orTimeout( 5000, "MILLISECONDS" )

"SECONDS"

TimeUnit.SECONDS

Second precision

future.orTimeout( 30, "SECONDS" )

"MINUTES"

TimeUnit.MINUTES

Minute precision

future.orTimeout( 5, "MINUTES" )

"HOURS"

TimeUnit.HOURS

Hour precision

future.orTimeout( 2, "HOURS" )

"DAYS"

TimeUnit.DAYS

Day precision

future.orTimeout( 1, "DAYS" )

Important Notes

  • Default Unit: When no unit is specified, milliseconds are assumed in multiple contexts. However, please see the specific context for default behavior.

  • Java Interop: You can pass actual java.util.concurrent.TimeUnit instances

  • Precision: Choose appropriate precision for your use case to avoid unnecessary overhead

For detailed examples of time units in specific contexts, see:

Last updated

Was this helpful?