BoxLang πŸš€ A New JVM Dynamic Language Learn More...

BX Effect

v1.1.0 BoxLang Modules

BX Effect

Build workflows with consistent error handling, retries, concurrency, and resource cleanup.

BX Effect is a BoxLang module for combining operations such as HTTP requests, database queries, and file processing into reusable programs. Define the work, choose how failures should be handled, and run it synchronously or asynchronously.

Use it when a workflow needs more than a single function call: retry a failed request, process a batch with a concurrency limit, supply services for testing, or release resources when work finishes or is interrupted. You can introduce it around one operation and build from there.

Inspired by Effect, BX Effect provides its own API for BoxLang applications.

Requirements

  • BoxLang 1.16.0+
  • Java 21+

Supported Features

Capability What you can do
Workflow compositionTransform results with map, chain operations with flatMap, and observe values with tap.
Error handlingRecover from expected errors while keeping unexpected defects and interruption distinguishable. Inspect complete outcomes with Exit and Cause.
Retries and timingRetry failed operations, repeat successful ones, add delays, and set timeouts with reusable Schedule policies.
ConcurrencyRun batches with a concurrency limit, race operations, and manage running work through Fibers. Integrate existing BoxFuture APIs.
Resource cleanupPair acquisition with release and run finalizers on success, failure, or interruption.
Dependency managementSupply services through Context and Layer, and reuse services across runs with ManagedRuntime.
CoordinationSignal completion with Deferred, limit access with Semaphore, buffer work with Queue, and broadcast through PubSub.
StreamsTransform and consume sequences on demand, including Queue and PubSub sources, with cleanup when consumption ends.
Testing and diagnosticsControl time with TestClock and observe runtime events with an optional observer or logging adapter.

Installation

You can refer to the official BoxLang modules docs for which runtime option may best suit your needs.

BoxLang CLI

install-bx-module bx-effect

CommandBox CLI

box install bx-effect

Usage

Example workflow

import models.effect.Effect@bxeffect;

program = Effect::sync( () => jsonDeserialize( '{"name":"Sam"}' ) )
	.map( user => "Hello, " & user.name & "!" );

println( Effect::runSync( program ) ); // Hello, Sam!

An Effect describes an operation and how to handle its result. sync wraps a function, map transforms its success value, and runSync executes the program. The supplied functions run when you execute the program; building the chain does not call them.

Use flatMap when the next step returns another Effect, and tap when you want to run an Effect without replacing the current success value.

Handle expected errors

Use Effect::try() to turn exceptions from an operation into errors your application can handle. This complete example falls back to a guest user when JSON parsing fails:

import models.effect.Effect@bxeffect;

program = Effect::try(
		try: () => jsonDeserialize( "invalid JSON" ),
		catch: error => { _tag: "InvalidUserData", message: error.message }
	)
	.catchTag( "InvalidUserData", error => Effect::succeed( { name: "Guest" } ) )
	.map( user => user.name );

println( Effect::runSync( program ) ); // Guest

The _tag field identifies an error for catchTag. Use Effect::fail() to report an expected error directly, or catchAll to recover from any expected error. Exceptions thrown by sync, transformations, or handlers are defects; catchAll and catchTag do not recover them. catchCause explicitly handles the complete failure, including defects and interruption.

Add .retry( Schedule::recurs( 3 ) ) before recovery to allow up to three retries after the initial attempt, importing models.effect.Schedule@bxeffect first. Retry applies to expected errors only. The schedules guide covers delays, backoff, repeat, and timeout policies.

Process a batch concurrently

forEach creates an Effect for each input and limits how many run at once. This example normalizes names and returns the results in input order:

import models.effect.Effect@bxeffect;

program = Effect::forEach(
	[ "sam", "alex", "jordan" ],
	( name, index ) => Effect::sync( () => uCase( name ) ),
	{ concurrency: 2 }
);

future = Effect::runFuture( program );
println( arrayToList( future.get() ) ); // SAM,ALEX,JORDAN

runFuture returns a BoxFuture without waiting for the program to finish; get() waits for its result. Replace the per-item operation with your own Effect to process files, make requests, or save records.

all and forEach fail fast by default. Set mode: "accumulate" in the options to wait for all branches and retain their failures in input order. See the concurrency guide for cancellation and coordination.

Choose how to run a program

Method Result
Effect::runSync( program ) The success value, or a thrown bxeffect.EffectFailureException.
Effect::runSyncExit( program ) An Exit containing the success value or complete failure Cause.
Effect::runFuture( program ) A BoxFuture that completes with the success value or exceptionally on failure.
Effect::runFutureExit( program ) A BoxFuture that completes with an Exit.
Effect::runFork( program ) A Fiber you can poll, join, or request to interrupt.

Choose an Exit method when you want to inspect failure as a value instead of catching an exception. Fiber interruption is best effort; scoped cleanup still runs. For services shared across repeated runs, use ManagedRuntime and close it when their lifetime ends.

Guides

Start with Getting started, then choose a guide for the next concern in your application:

Guide Learn how to…
Error handling and outcomesDistinguish expected errors, defects, and interruption; work with Exit, Cause, and Attempt.
Resources and cleanupAcquire resources, register release actions, and handle cleanup failures.
Services and LayersDeclare dependencies, provide implementations, and compose service setup.
ManagedRuntimeReuse application services across runs and shut them down safely.
Concurrency and coordinationUse BoxFutures, Fibers, Deferred, Semaphore, Queue, and PubSub.
StreamsBuild and consume sequences with demand-driven processing and resource cleanup.
Schedules and timeoutsConfigure retries, repeats, and timeouts, and test timing with TestClock.
Runtime observabilityObserve runtime events and connect an existing logger.
BoxLang integration recipesWrap HTTP, database, file, and asynchronous operations.
Incremental adoptionIntroduce Effects into existing application code.
Public API and compatibilityFind the supported classes and methods for the 1.x API.

Contributing & Testing

Bug reports, documentation improvements, and pull requests are welcome through GitHub. Include a small reproduction and your BoxLang and Java versions when reporting a bug. Add focused tests for behavior changes and update the relevant guide when public behavior changes.

After cloning the repository, install the development dependencies and run the TestBox suite from the repository root:

# Using CommandBox
box install
box run-script test

The suite runs through the BoxLang CLI (via CommandBox or an OS install of BoxLang if preferred). CI runs the TestBox suite and module-setting checks on BoxLang latest and snapshot. For performance work, see the benchmarks.

Changelog

All notable changes to BX Effect are documented here.

1.1.0 - 9-23-2026

  • Add acquireUseRelease, fromExit, failCause, die, and catchTags, plus sequential Stream takeWhile, scan, and grouped.
  • Retain failed child cleanup through races, fail-fast collections, root shutdown, and managed runtime shutdown. Serialize worker interruption with wait unregistration.
  • Reject null Queue and PubSub messages lazily, align ServiceTag equality and hashing with native BoxLang keys, and use a monotonic live Clock for recurrence.
  • Improve Cause traversal, discarded concurrent collections, and deep Stream concatenation without changing their public results.
  • Define the 1.x public compatibility boundary, check TestBox JSON totals in CI, and test on BoxLang latest and snapshot. Verify installed-module activation before publication.

1.0.0 β€” 09-08-2026

Initial release of BX Effect for BoxLang 1.16.0+ and Java 21+, bringing lazy workflow composition, explicit error handling, structured concurrency, and resource safety to BoxLang applications. Inspired by Effect, with a dedicated BoxLang API built on native runtime facilities.

Effects and error handling

  • Lazy constructors and reusable composition through map, flatMap, tap, zip, and zipWith, with synchronous, BoxFuture, and Fiber execution boundaries.
  • Distinct expected-failure, defect, and interruption channels represented by immutable Cause trees and Exit outcomes; Result for expected-error values and integration with native Attempt for presence and absence.
  • Expected-error recovery and transformation, tagged and conditional recovery, explicit full-Cause handling, and outcome inspection with tapError, tapCause, and exit.

Resources and services

  • Scoped acquisition and release, ensuring, and onExit, with idempotent LIFO finalization on every exit and preservation of cleanup failures.
  • Immutable Context, named ServiceTag identities, and lazy, composable Layer recipes with scoped services, nested Context restoration, deterministic missing-service diagnostics, and runtime-local sharing across child Fibers.
  • Explicitly owned ManagedRuntime for one application Layer shared across repeated runs, including concurrent first-build sharing, retry after failed builds, and shutdown that waits for run cleanup before releasing services.

Concurrency and coordination

  • Native BoxFuture integration and BoxLang executor reuse, with non-blocking runFuture calls, scoped child Fibers, and best-effort interruption.
  • Concurrent all and forEach with configurable limits, fail-fast defaults, and failure accumulation in input order; first-completion racing and first-success selection.
  • One-shot Deferred, scoped Semaphore permits, bounded and unbounded Queue buffers, and PubSub broadcast with explicit subscription ownership, interruptible backpressure, and shutdown semantics.

Schedules and streams

  • Expected-error retries, successful repetition, sleep, and timeouts, with recurrence counts, spaced and fixed timing, exponential backoff, jitter, and composable Schedule policies.
  • Injectable live Clock and synchronized TestClock, shipped for downstream deterministic testing without a TestBox dependency.
  • Reusable, demand-driven Stream descriptions with sequential transformations, effectful mapping, filtering, concatenation, recovery, and collection, folding, or draining consumers. Each consumption owns a fresh cursor and cleans up on completion, failure, early termination, or interruption.
  • Resource-backed Stream sources and Queue/PubSub bridges with explicit ownership.

Diagnostics, performance, and verification

  • Optional runtime observer and native logging adapter for execution, Fiber, retry, cleanup, and unhandled-defect events; observer failures cannot alter outcomes or prevent cleanup.
  • Iterative, stack-safe interpretation with a JDK ArrayDeque continuation stack, direct map handling, lazy executor resolution, and reusable concurrent completion futures. Cause inspection, transformation, and rendering are also stack-safe.
  • Correctness-checked benchmarks for deep composition, async work, concurrency, Layer sharing, managed runtimes, Streams, Context, and Scope, with warmup, monotonic timing, and minimum/median reporting.
  • Focused TestBox contracts and CI for BoxLang 1.16.0 and latest, including executor overrides, package inspection, and isolated installed-consumer checks.
  • Public API inventory and guides for error handling, resources, services, concurrency, Streams, scheduling, observability, native integrations, incremental adoption.

Correctness fixes included in 1.0

  • Correct native live delays and zero-duration TestClock futures, and handle fixed-schedule overruns without replaying missed intervals.
  • Protect successful acquisition handoffs from interruption and release PubSub, Semaphore, and Stream resources before following work or outer recovery.
  • Memoize Layer output bindings without leaking the first caller's input Context.
  • Strengthen Fiber cancellation so it reaches active nested futures and known interruptible waits while preserving interruption as a distinct failure channel.
  • Add focused regressions for timing, acquisition, cleanup, and Layer isolation, plus elapsed-time lower-bound checks for delay benchmarks.

$ box install bx-effect

No collaborators yet.
     
  • {{ getFullDate("2026-09-24T02:35:58Z") }}
  • {{ getFullDate("2026-09-24T02:35:59Z") }}
  • 10
  • 0