Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

WebSocket Provider

Basic Usage

import sh.brane.rpc.Brane;
 
// Create a WebSocket-based client
Brane client = Brane.builder()
    .wsUrl("wss://ethereum.publicnode.com")
    .build();
 
// Use like any other client
var latestBlock = client.getLatestBlock();
System.out.println("Block #" + latestBlock.number());
 
// Always close when done
client.close();

For simple connection without additional configuration:

import sh.brane.rpc.Brane;
import sh.brane.rpc.WebSocketProvider;
 
// If you need direct provider access
WebSocketProvider provider = WebSocketProvider.create("wss://ethereum.publicnode.com");
Brane client = Brane.builder()
    .provider(provider)
    .build();

Advanced Configuration

For production use cases, you'll want fine-grained control over connection behavior, timeouts, and performance tuning.

WebSocketConfig

For fine-grained control over WebSocket behavior, configure via WebSocketConfig:

import sh.brane.rpc.Brane;
import sh.brane.rpc.WebSocketConfig;
import sh.brane.rpc.WebSocketConfig.WaitStrategyType;
import sh.brane.rpc.WebSocketProvider;
import java.time.Duration;
 
var config = WebSocketConfig.builder("wss://eth.example.com")
    .maxPendingRequests(32768)              // Max concurrent requests
    .ringBufferSize(8192)                   // Disruptor buffer (power of 2)
    .waitStrategy(WaitStrategyType.YIELDING) // Low latency mode
    .defaultRequestTimeout(Duration.ofSeconds(30))
    .connectTimeout(Duration.ofSeconds(10))
    .ioThreads(1)                           // Usually 1 is optimal
    .writeIdleTimeout(Duration.ofSeconds(15)) // Ping if no writes
    .readIdleTimeout(Duration.ofSeconds(30))  // Close if no reads
    .maxFrameSize(4 * 1024 * 1024)          // 4MB for large responses
    .build();
 
WebSocketProvider provider = WebSocketProvider.create(config);
Brane client = Brane.builder()
    .provider(provider)
    .build();

Configuration Reference

OptionDefaultDescription
maxPendingRequests65,536Maximum concurrent in-flight requests. Must be power of 2.
ringBufferSize4,096Disruptor ring buffer size. Increase for high throughput.
waitStrategyYIELDINGYIELDING = low latency, high CPU. BLOCKING = low CPU, higher latency.
defaultRequestTimeout60 secTimeout for requests without explicit timeout.
connectTimeout10 secWebSocket handshake timeout.
ioThreads1Netty I/O threads. 1 is usually optimal (avoids context switching).
writeIdleTimeout15 secSend WebSocket ping if no data written for this duration. Keeps connection alive through NAT.
readIdleTimeout30 secClose connection if no data received for this duration. Detects dead connections.
maxFrameSize64 KBMaximum WebSocket frame size. Range: 1 byte to 16 MB. Increase for large eth_getLogs responses.
ringBufferSaturationThreshold0.10Warn via Metrics.onRingBufferSaturation() when buffer capacity falls below this fraction (0.0-1.0).

Request Timeouts

Every async request has a configurable timeout to prevent hanging requests.

Default Timeout

The default timeout comes from WebSocketConfig.defaultRequestTimeout() (60 seconds by default):

// Uses the default timeout from config
var response = provider.sendAsync("eth_blockNumber", List.of()).get();

Per-Request Timeout

Override the timeout for individual requests:

import java.time.Duration;
 
// Short timeout for time-sensitive operations
var gasPrice = provider.sendAsync("eth_gasPrice", List.of(), Duration.ofSeconds(5))
    .get();
 
// Longer timeout for slow methods
var logs = provider.sendAsync("eth_getLogs", List.of(filter), Duration.ofSeconds(120))
    .get();

Connection Keepalive & Idle Timeouts

WebSocket connections can be silently dropped by NAT devices, firewalls, or load balancers after 30-60 seconds of inactivity. Brane automatically handles this with configurable idle timeouts.

How It Works

  • Write idle: When no data has been written for writeIdleTimeout, a WebSocket ping frame is sent to keep the connection alive through NAT devices.
  • Read idle: When no data has been received for readIdleTimeout, the connection is considered dead and closed. The provider transitions to RECONNECTING state.

Configuration Examples

// Standard settings (defaults work for most cases)
var config = WebSocketConfig.builder("wss://...")
    .writeIdleTimeout(Duration.ofSeconds(15))  // Ping every 15s of inactivity
    .readIdleTimeout(Duration.ofSeconds(30))   // Close if no response for 30s
    .build();
 
// Aggressive NAT environments (some mobile networks)
var config = WebSocketConfig.builder("wss://...")
    .writeIdleTimeout(Duration.ofSeconds(10))
    .readIdleTimeout(Duration.ofSeconds(20))
    .build();
 
// Disable idle timeouts (not recommended)
var config = WebSocketConfig.builder("wss://...")
    .writeIdleTimeout(Duration.ZERO)
    .readIdleTimeout(Duration.ZERO)
    .build();

Subscriptions

WebSocket enables real-time event subscriptions via eth_subscribe.

New Block Headers

Subscribe to new blocks as they are mined:

String subscriptionId = provider.subscribe("newHeads", null, event -> {
    System.out.println("New block: " + event);
});
 
// Later, unsubscribe
provider.unsubscribe(subscriptionId);

Contract Event Logs

Subscribe to specific contract events:

import java.util.List;
import java.util.Map;
 
// Filter for Transfer events on USDC
Map<String, Object> filter = Map.of(
    "address", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "topics", List.of("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
);
 
String subscriptionId = provider.subscribe("logs", List.of(filter), event -> {
    System.out.println("Transfer detected: " + event);
});

Pending Transactions

Monitor the mempool for new pending transactions:

String subscriptionId = provider.subscribe("newPendingTransactions", null, event -> {
    System.out.println("Pending tx: " + event);
});

Custom Callback Executor

By default, subscription callbacks run on virtual threads to prevent blocking the Netty I/O thread.

For custom threading behavior:

import java.util.concurrent.Executors;
 
// Use a bounded thread pool for callbacks
provider.setSubscriptionExecutor(
    Executors.newFixedThreadPool(4)
);
 
// Or run directly on the Netty I/O thread (use with caution)
provider.setSubscriptionExecutor(Runnable::run);

High-Performance Batching

For high-throughput scenarios, use sendAsyncBatch() which uses an LMAX Disruptor ring buffer for optimal batching:

import java.util.concurrent.CompletableFuture;
import java.util.List;
 
// Fire many requests in rapid succession
CompletableFuture<?>[] futures = new CompletableFuture[1000];
for (int i = 0; i < 1000; i++) {
    futures[i] = provider.sendAsyncBatch("eth_blockNumber", List.of());
}
 
// Wait for all responses
CompletableFuture.allOf(futures).join();

This batches network writes together, reducing syscall overhead.

Async vs Batch Methods

MethodUse CaseLatencyThroughput
sendAsync()Individual requestsOptimalGood
sendAsyncBatch()Bulk requestsGoodOptimal

Rule of thumb: Use sendAsync() normally. Switch to sendAsyncBatch() when sending many requests in a tight loop.

Backpressure & Error Handling

The provider has built-in backpressure to prevent overwhelming the connection.

Too Many Pending Requests

When you exceed maxPendingRequests, new requests fail immediately with an RpcException:

try {
    var response = provider.sendAsync("eth_blockNumber", List.of()).get();
} catch (ExecutionException e) {
    if (e.getCause() instanceof RpcException rpc) {
        if (rpc.getMessage().contains("Too many pending requests")) {
            // Back off and retry
            Thread.sleep(100);
        }
    }
}

Connection Lost

The provider attempts to reconnect automatically. If the connection is lost while requests are in-flight, those futures will complete exceptionally.

Connection State Machine

The WebSocketProvider exposes its connection state for monitoring and handling edge cases:

import sh.brane.rpc.WebSocketProvider;
import sh.brane.rpc.WebSocketProvider.ConnectionState;
 
var provider = WebSocketProvider.create("wss://...");
 
// Check current state
ConnectionState state = provider.getConnectionState();
System.out.println("Connection state: " + state);

Connection States

StateDescription
CONNECTINGInitial connection in progress
CONNECTEDWebSocket is connected and operational
RECONNECTINGConnection lost (network error or read idle timeout), attempting to reconnect
CLOSEDProvider has been closed

Handling Reconnection

During RECONNECTING state, requests may fail temporarily. For resilient applications:

ConnectionState state = provider.getConnectionState();
 
if (state == ConnectionState.RECONNECTING) {
    // Wait or use fallback strategy
    log.warn("WebSocket reconnecting, request may fail");
}
 
if (state == ConnectionState.CLOSED) {
    // Provider is shut down, need a new instance
    throw new IllegalStateException("Provider is closed");
}

Client Types

The builder creates different client types based on configuration:

import sh.brane.rpc.Brane;
import sh.brane.core.crypto.PrivateKeySigner;
import sh.brane.core.builder.TxBuilder;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
import java.math.BigDecimal;
 
// Read-only client (Brane.Reader) - for queries and subscriptions
Brane.Reader reader = Brane.builder()
    .wsUrl("wss://ethereum.publicnode.com")
    .buildReader();
 
var balance = reader.getBalance(new Address("0x..."));
var sub = reader.onNewHeads(header -> System.out.println("Block: " + header.number()));
 
// Signing client (Brane.Signer) - can send transactions
Brane.Signer signer = Brane.builder()
    .wsUrl("wss://ethereum.publicnode.com")
    .signer(new PrivateKeySigner("0x..."))
    .buildSigner();
 
// Create and send a transaction over WebSocket
var request = TxBuilder.eip1559()
    .to(new Address("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"))
    .value(Wei.fromEther(new BigDecimal("0.01")))
    .build();
 
var receipt = signer.sendTransactionAndWait(request);

When to Use WebSocket

ScenarioRecommended
Real-time subscriptionsWebSocket
High-frequency trading / MEVWebSocket
Long-running servicesWebSocket
Simple read operationsHTTP or WebSocket
Serverless / LambdaHTTP (no persistent connection)
Mobile appsHTTP (battery efficiency)