Skip to content

Configuring WebSocket client

The WebSocketClient is the default client used to connect to the Fluxzero Runtime over WebSocket. It provides full access to the event store, message gateways, tracking, search, scheduling, and key-value storage subsystems via configurable, high-throughput sessions.

To configure and instantiate a WebSocket-backed client:

WebSocketClient client = WebSocketClient.newInstance(
WebSocketClient.ClientConfig.builder()
.runtimeBaseUrl("wss://my.flux.host")
.name("my-service")
.build());
Fluxzero flux = DefaultFluxzero.builder().build(client);

This is the most common setup for production and shared environments. It connects to a remote Fluxzero Runtime via the runtime base URL, which must point to the desired deployment.


The ClientConfig class defines all connection, routing, compression, and tracking parameters. It is fully immutable and can be created or extended using the toBuilder() pattern.

Key options include:

SettingDescriptionDefault
runtimeBaseUrlBase URL for all subsystems
(e.g. wss://my.flux.host)
FLUXZERO_BASE_URL or FLUX_BASE_URL
nameName of the applicationFLUXZERO_APPLICATION_NAME or legacy alias
applicationIdOptional app IDFLUXZERO_APPLICATION_ID or legacy alias
idUnique client/process instance IDFLUXZERO_CLIENT_ID, legacy alias; otherwise task-ID-prefixed UUID or UUID
supportedCompressionAlgorithmsPreferred and fallback compression algorithmsZSTD, then LZ4
pingDelay / pingTimeoutHeartbeat intervals for WebSocket health10s / 15s
maxConcurrentRuntimeWebSocketMessagesMessages decoded or waiting for completion-dispatcher admission per session3
maxRetainedRuntimeWebSocketMessagesTotal assembling, compressed-pending, decode/admission, and active messages128
maxRetainedRuntimeWebSocketBytesTotal compressed runtime wire bytes retained per session64 MiB
maxConcurrentRuntimeResultCompletionsAdmitted groups and result completions/continuations per client8
runtimeIngressStallCloseTimeoutOptional close delay after ingress is diagnosed as stalledDisabled
aggregateHistoryMaxFetchBytesSerialized event-payload bytes requested per aggregate-history pageCount-only in compatibility mode; 100 MiB from defaults version 2026.09.10
disableMetricsWhether to suppress all outgoing metricsfalse
typeFilterOptional message type restrictionnull

FLUXZERO_TASK_ID identifies the hosting platform task or pod; it is not itself a unique process incarnation. When present, the SDK uses it as the recognizable prefix of a generated client ID and publishes the unchanged value as authoritative $taskId correlation metadata. The generated client ID remains stable across WebSocket reconnects, but a newly constructed client receives a new UUID suffix. Set FLUXZERO_CLIENT_ID only when supplying an explicitly unique client-instance ID; FLUX_CLIENT_ID remains available as a legacy alias.

Aggregate histories use count-bounded pages of at most 8,192 events. Set fluxzero.eventsourcing.maxFetchBytes or ClientConfig.aggregateHistoryMaxFetchBytes(...) to add a cumulative serialized payload-byte bound per page; fluxzero.defaults.version >= 2026.09.10 selects 100 MiB when neither is set. The property accepts 0 to retain count-only paging and is also available as FLUXZERO_EVENTSOURCING_MAX_FETCH_BYTES. Metadata, the response envelope, and WebSocket compression are outside this payload measure. One individually oversized event still flows on its own, and the client continues after short byte-limited pages until the Runtime returns an empty page. A Runtime from before this protocol extension ignores the optional byte request and remains count-bounded only; the SDK still reads its pages correctly.

In compatibility mode, failed WebSocket connections retry every second. Set fluxzero.websocket.reconnectBackoff.enabled=true, or use fluxzero.defaults.version >= 2026.09.09, to use equal jitter over capped exponential ceilings of 1, 2, 4, 8, then 16 seconds. A successful connection resets the retry cycle. Set the dedicated property to false to keep fixed retries on a newer defaults profile. The conventional environment-variable name is FLUXZERO_WEBSOCKET_RECONNECT_BACKOFF_ENABLED.


Runtime responses are decoded outside the WebSocket protocol callback path. Up to three complete messages per session are decoded or wait for bounded dispatcher admission concurrently by default. A permit is released after safe admission, while the message remains retained until functional completion. A client-wide completion dispatcher admits and actively completes at most eight message groups/results per client by default. Customer callbacks never run on a decode worker. This keeps pong and decode progress independent from slow functional continuations while preserving bounded resource use.

The SDK retains at most 128 runtime messages or 64 MiB of compressed wire data per physical session by default. Messages remain retained until functional processing completes, including synchronous code triggered by completing a request future. When a retained bound is reached, the JDK WebSocket adapter pauses receive demand and resumes it after capacity is released. Normal capacity pressure does not close or reconnect an otherwise healthy session.

The byte envelope is a retained-work credit limit and is not allocated up front. Tracking’s default maxFetchBytes is 100 MiB of serialized message payload before WebSocket compression and envelope overhead. A single response can therefore exceed the transport envelope; it may still proceed when it is the only retained message, while later responses wait for capacity.

The limits can be configured explicitly. The examples below restate the defaults:

ClientConfig config = ClientConfig.builder()
.maxConcurrentRuntimeWebSocketMessages(3)
.maxRetainedRuntimeWebSocketMessages(128)
.maxRetainedRuntimeWebSocketBytes(64L * 1024 * 1024)
.maxConcurrentRuntimeResultCompletions(8)
.runtimeIngressStallCloseTimeout(Duration.ZERO)
.build();

Operational properties use the following canonical names:

PropertyPurpose
fluxzero.runtime.ingress.maxConcurrencyConcurrent complete runtime messages per session
fluxzero.runtime.ingress.maxRetainedMessagesTotal retained runtime messages per session
fluxzero.runtime.ingress.maxRetainedBytesTotal retained compressed wire bytes per session
fluxzero.runtime.ingress.maxCompletionConcurrencyConcurrent result completions per client
fluxzero.runtime.ingress.stallCloseTimeoutOptional ISO-8601 stall close delay, for example PT30S

Explicit builder values take precedence over properties. Legacy fluxzero.websocket.runtime.* and FLUXZERO_WEBSOCKET_RUNTIME_MAX_* aliases remain supported for the three WebSocket capacity settings. There is no unbounded or disable mode. Set message concurrency to one for serial decode, and result-completion concurrency to one only when functional callbacks must also be serial, while keeping protocol isolation.

With transport metrics enabled, a retained session without a functional completion for one pingTimeout emits RUNTIME_INGRESS_STALLED. An idle session without retained work is never stalled. The next completed message or an empty retained queue emits RUNTIME_INGRESS_RECOVERED. Independently of metric publication, a positive runtimeIngressStallCloseTimeout enables the progress watchdog and closes the session after that additional period without progress. The timeout is disabled by default. Normal ping-timeout detection remains active whenever receive demand is open. Peer close and transport I/O errors remain active during local backpressure; a silent connection failure during a persistent local pause is bounded only when the stall-close timeout is configured.

Set fluxzero.websocket.transportMetrics.enabled=true to publish sparse transport diagnostics such as RUNTIME_INGRESS_BACKPRESSURED, RUNTIME_INGRESS_STALLED, RUNTIME_INGRESS_RECOVERED, RUNTIME_INGRESS_OVERFLOW, RUNTIME_EXECUTOR_REJECTED, and PING_TIMEOUT. This diagnostic is disabled by default. Diagnostics report the effective transport and completion worker modes and both configured concurrency bounds. At most one transport-metric publication per client runs on its dedicated worker. Additional diagnostics are dropped while that publication is occupied, and its one-second deadline releases metric ordering gates and requests worker interruption. A non-cooperative publisher can occupy only that single slot; it cannot queue more metric work, consume result-completion workers, or delay client shutdown. Increase retained messages to absorb verified bursts of small responses. A wider retained window does not add runtime-message workers, but can improve burst throughput by reducing receive-demand pauses and keeping existing workers supplied. Increasing retained limits raises worst-case memory use. Message concurrency controls simultaneous decode/allocation; result-completion concurrency separately controls admitted decoded work and customer-callback pressure.

The executor policy itself does not change: Java 25 and newer use virtual completion workers, while Java 21 through 24 use the existing lazily populated fixed platform-thread pool. Both routes default to eight. Explicit completion concurrency still wins on both routes, so a Java 25 application can opt into 32 after validating its workload.


Flux opens multiple WebSocket sessions to handle parallel workloads. You can tune the number of sessions per subsystem:

ClientConfig config = ClientConfig.builder()
.eventSourcingSessions(2)
.searchSessions(3)
.gatewaySessions(Map.of(COMMAND, 2, EVENT, 1))
.build();

Each session can multiplex multiple consumers or producers under the hood. Use more sessions to improve parallelism and isolation across critical workloads.


Tracking clients can use local caches to optimize polling performance when many consumers are tracking the same topic or message type.

ClientConfig config = ClientConfig.builder()
.trackingConfigs(Map.of(
EVENT, TrackingClientConfig.builder()
.sessions(2)
.cacheSize(1000)
.build()))
.build();

The cacheSize determines how many messages are buffered in-memory per topic. This helps reduce round-trips to the Runtime and can significantly boost performance in high-fanout projections or handlers.


Once created, the client is passed into the builder:

Fluxzero flux = DefaultFluxzero.builder()
.makeApplicationInstance(true)
.build(webSocketClient);

For testing or lightweight local development, use the in-memory LocalClient instead:

Fluxzero flux = DefaultFluxzero.builder().build(LocalClient.newInstance());

This simulates the entire platform in-memory without external dependencies.



© 2026 Fluxzero