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.
Creating a WebSocketClient
Section titled “Creating a WebSocketClient”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);val client = WebSocketClient.newInstance( WebSocketClient.ClientConfig.builder() .runtimeBaseUrl("wss://my.flux.host") .name("my-service") .build())
val 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.
Client configuration (ClientConfig)
Section titled “Client configuration (ClientConfig)”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:
| Setting | Description | Default |
|---|---|---|
runtimeBaseUrl | Base URL for all subsystems (e.g. wss://my.flux.host) | FLUXZERO_BASE_URL or FLUX_BASE_URL |
name | Name of the application | FLUXZERO_APPLICATION_NAME or legacy alias |
applicationId | Optional app ID | FLUXZERO_APPLICATION_ID or legacy alias |
id | Unique client/process instance ID | FLUXZERO_CLIENT_ID, legacy alias; otherwise task-ID-prefixed UUID or UUID |
supportedCompressionAlgorithms | Preferred and fallback compression algorithms | ZSTD, then LZ4 |
pingDelay / pingTimeout | Heartbeat intervals for WebSocket health | 10s / 15s |
maxConcurrentRuntimeWebSocketMessages | Messages decoded or waiting for completion-dispatcher admission per session | 3 |
maxRetainedRuntimeWebSocketMessages | Total assembling, compressed-pending, decode/admission, and active messages | 128 |
maxRetainedRuntimeWebSocketBytes | Total compressed runtime wire bytes retained per session | 64 MiB |
maxConcurrentRuntimeResultCompletions | Admitted groups and result completions/continuations per client | 8 |
runtimeIngressStallCloseTimeout | Optional close delay after ingress is diagnosed as stalled | Disabled |
aggregateHistoryMaxFetchBytes | Serialized event-payload bytes requested per aggregate-history page | Count-only in compatibility mode; 100 MiB from defaults version 2026.09.10 |
disableMetrics | Whether to suppress all outgoing metrics | false |
typeFilter | Optional message type restriction | null |
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 ingress flow control
Section titled “Runtime ingress flow control”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();val config = ClientConfig.builder() .maxConcurrentRuntimeWebSocketMessages(3) .maxRetainedRuntimeWebSocketMessages(128) .maxRetainedRuntimeWebSocketBytes(64L * 1024 * 1024) .maxConcurrentRuntimeResultCompletions(8) .runtimeIngressStallCloseTimeout(Duration.ZERO) .build()Operational properties use the following canonical names:
| Property | Purpose |
|---|---|
fluxzero.runtime.ingress.maxConcurrency | Concurrent complete runtime messages per session |
fluxzero.runtime.ingress.maxRetainedMessages | Total retained runtime messages per session |
fluxzero.runtime.ingress.maxRetainedBytes | Total retained compressed wire bytes per session |
fluxzero.runtime.ingress.maxCompletionConcurrency | Concurrent result completions per client |
fluxzero.runtime.ingress.stallCloseTimeout | Optional 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.
Subsystem sessions
Section titled “Subsystem sessions”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();val config = ClientConfig.builder() .eventSourcingSessions(2) .searchSessions(3) .gatewaySessions(mapOf(COMMAND to 2, EVENT to 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 configuration
Section titled “Tracking configuration”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();val config = ClientConfig.builder() .trackingConfigs( mapOf( EVENT to 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.
Integration with FluxzeroBuilder
Section titled “Integration with FluxzeroBuilder”Once created, the client is passed into the builder:
Fluxzero flux = DefaultFluxzero.builder() .makeApplicationInstance(true) .build(webSocketClient);val flux = DefaultFluxzero.builder() .makeApplicationInstance(true) .build(webSocketClient)Local alternative
Section titled “Local alternative”For testing or lightweight local development, use the in-memory LocalClient instead:
Fluxzero flux = DefaultFluxzero.builder().build(LocalClient.newInstance());val flux = DefaultFluxzero.builder().build(LocalClient.newInstance())This simulates the entire platform in-memory without external dependencies.
© 2026 Fluxzero