Errors and Troubleshooting
Check operation failures, handle asynchronous server errors, and diagnose common SDK problems.
Before you start: follow Connection and lifecycle. Register an error listener once per channel, and also check the futures that your calls return.
Every failure that the SDK detects is a CelerisException, an unchecked exception with a stable code(). Match the code with code() == ErrorCode.TIMEOUT or a switch, never on message text. Messages name the field and the rule that failed. They never repeat your input, a credential, or server text, and they never carry a cause. Errors that the server sends are a ServerErrorException instead.
1. Check the call you made
chat.publish(Payloads.text("Hello"))
.whenComplete(
(ignored, failure) -> {
if (failure == null) {
return;
}
Throwable cause = failure instanceof CompletionException ? failure.getCause() : failure;
if (cause instanceof CelerisException sdkFailure
&& sdkFailure.code() == ErrorCode.NOT_CONNECTED) {
System.out.println("Wait until connected before sending.");
} else {
System.out.println(cause.getMessage());
}
});
Methods that return a future never throw, except NullPointerException for a null argument. Every failure completes the future. join() rethrows the failure inside a CompletionException, and a dependent stage sees it wrapped too. Unwrap it with getCause(). Check connect(), publish, and presenceList where you call them. A failed initial connect, like a failed presence query, fails its future and does not also reach onError.
Other calls throw at once. ClientOptions.Builder.build(), client.channel, and channel.segment throw CONFIGURATION for invalid input. subscribe throws NOT_CONNECTED when the channel is closing or closed. A future that you cancel completes with the JDK's CancellationException. A future past an orTimeout deadline completes with TimeoutException. Neither exception is a CelerisException.
2. Handle server errors separately
Registration errorListener =
channel
.events()
.onError(
error -> {
if (!(error instanceof ServerErrorException serverError)) {
System.out.println(error.getMessage());
return;
}
System.out.println(
serverError.type()
+ " "
+ serverError.subType().orElse("")
+ " "
+ serverError.getMessage());
switch (serverError.type()) {
case ServerErrorException.PERMISSION_DENIED_ERROR ->
System.out.println("Check the server-issued channel and segment permissions.");
case ServerErrorException.MESSAGE_SIZE_LIMIT_ERROR ->
System.out.println("Reduce the payload size.");
case ServerErrorException.RATE_LIMIT_ERROR ->
// The SDK pauses and resends recent commands by itself.
System.out.println("Rate limited; publish less often if this repeats.");
default -> System.out.println("The server reported an error.");
}
});
// Later:
errorListener.close();
A ServerErrorException carries type(), subType(), getMessage(), and resource(). type() is a String, with constants for the six known types. subType() is an Optional that names the command it answers. getMessage() is the server's own text. resource() is an Optional of what that command names, such as the segment.
The exception has no code(). Match its type(). Error types are open-ended, so keep a default branch for types that a newer server adds. A resource can be a String, an Integer, a Long, or an unmodifiable List of these. Check its type before you use it.
A permission denial can identify a command and a segment. It does not identify which of several identical publishes caused the error. The SDK matches presence-query errors to their query, and those errors fail presenceList's future instead. Server error text is for people, not for programs. Show it as text, never as raw HTML. Server errors leave the connection open.
3. Choose the right response
| Code | Response |
|---|---|
CONFIGURATION | Fix the invalid argument or credential. A retry with unchanged input does not help |
NOT_CONNECTED | Wait for a connection, then try again. A publish waits in the queue only while the channel is reconnecting |
TIMEOUT | Check the credential endpoint and network, or the query deadline |
CANCELLED | The channel closed, or a newer attempt replaced this one, during the operation. Do not restart it blindly |
TRANSPORT | Check network and credential setup. This does not prove an authentication failure |
BACKPRESSURE | Slow production. The publish queue (publishQueueSize, 64 by default) is full, or a presence query found the writer full or paused |
OPERATION_IN_PROGRESS | Do not start simultaneous connects or presence queries on one channel |
DELIVERY_UNKNOWN | Acceptance is uncertain. Do not blindly resend an irreversible operation |
PROTOCOL | One message was dropped. The connection remains usable. field() and offset() locate the failure |
ServerErrorException | An error that the server sent. See step 2 |
ErrorCode.code() gives the name that every Celeris SDK uses for a code, such as NotConnected. The SDK catches a listener that throws and reports it through onError as TRANSPORT. The SDK does not report an error listener that throws. A null listener registration throws NullPointerException at once, as a programming error.
4. Diagnose common symptoms
| Symptom | First checks |
|---|---|
| First connect fails and nothing retries | This is expected. Call connect() again from failed |
| Socket connects but messages are absent | Channel/segment match, a held subscription, and read permission |
| Sender sees no received copy | allowEcho defaults to false |
| Publish completes, then an error arrives | Expected for server refusals. Completion means only local acceptance |
presenceList always fails with TIMEOUT | A listener waits for it. Compose on the future or use your own thread |
| Presence events miss a user | You get events only for joins and leaves while you hold presence. Query a cluster-wide listing |
| The server denied credentials, but the error says Transport | The SDK reports every refused handshake as TRANSPORT |
| Messages are absent after recovery | Replay is bounded. Reload authoritative application state |
presenceList fails with CONFIGURATION | page must be at least 1 and perPage 1 to 100 |
| A closed channel cannot reconnect | Obtain a new channel from the client |
| A program whose only work is message receipt exits | The SDK's threads are daemon threads. Keep your main thread alive |
See Reconnection and Recovery for which failures retry. Do not implement your own retry timer while the channel is in the reconnecting state.
Next: Server-side usage, or the Java client API for the full error surface.