redis/jedis · error · JedisException
Unexpected message
Error message
Unexpected message : <reply>
What it means
JedisShardedPubSubBase.process() treats a bare byte[] reply as a synchronous command acknowledgement, which must be consumed by a registered resultHandler. If resultHandler.poll() returns null — no handler is queued — the reply is uninterpretable in the subscription loop and it throws JedisException("Unexpected message : " + <reply>).
Solutions
- Do not issue commands on the pub/sub connection while the process() loop is running.
- Create a fresh JedisPubSubBase per subscription session; don't reuse after unsubscribe.
- Synchronize subscribe/unsubscribe calls so handlers are registered before their acks arrive.
- Catch JedisException and reconnect/re-subscribe on a clean connection.
Example fix
// before
pubSub.ssubscribe(j, ch);
otherThread.set("k", "v"); // reply lands in pub/sub loop -> Unexpected message
// after
pubSub.ssubscribe(j, ch); // all other commands on separate connections
j2.set("k", "v"); // use a different Jedis instance Defensive patterns
Strategy: try-catch
Validate before calling
// ensure no concurrent command can target the pub/sub connection assert !jedis.isInMulti();
Type guard
boolean isCommandAck(Object reply) { return reply instanceof byte[]; } Try / catch
try {
shardedPubSub.proceed(jedis, channel);
} catch (JedisException e) {
if (e.getMessage().startsWith("Unexpected message")) {
jedis.close(); // re-create connection and subscription
} else throw e;
} Prevention
- Route all other commands to separate connections while subscribed.
- Serialize subscribe/unsubscribe calls; never call them from multiple threads.
- Create a new pub/sub object per session; do not reuse after teardown.
- Treat any pub/sub JedisException as fatal to that connection.
When it happens
Trigger: proceed() loop receives a plain byte[] reply while no command result handler is registered — e.g. an unsolicited status/error reply or an ack arriving after the corresponding handler was already consumed.
Common situations: Calling non-subscribe commands on the same connection from another thread mid-subscription; duplicate acknowledgements after subscribe/unsubscribe races; connection reuse across pub/sub sessions.
Related errors
- Blocking pub/sub operations are not supported on…
- Unknown message type
- Unexpected message
- Unknown message type
- Unknown message type
AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08).
Data as JSON: /api/errors/0ba3e265b5120f47.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/redis/clients/jedis/JedisShardedPubSubBase.java:109
onSSubscribe(enchannel, subscribedChannels);
} else if (Arrays.equals(SUNSUBSCRIBE.getRaw(), resp)) {
subscribedChannels = ((Long) listReply.get(2)).intValue();
final byte[] bchannel = (byte[]) listReply.get(1);
final T enchannel = (bchannel == null) ? null : encode(bchannel);
onSUnsubscribe(enchannel, subscribedChannels);
} else if (Arrays.equals(SMESSAGE.getRaw(), resp)) {
final byte[] bchannel = (byte[]) listReply.get(1);
final byte[] bmesg = (byte[]) listReply.get(2);
final T enchannel = (bchannel == null) ? null : encode(bchannel);
final T enmesg = (bmesg == null) ? null : encode(bmesg);
onSMessage(enchannel, enmesg);
} else {
throw new JedisException("Unknown message type: " + firstObj);
}
} else if (reply instanceof byte[]) {
Consumer<Object> resultHandler = authenticator.resultHandler.poll();
if (resultHandler == null) {
throw new JedisException("Unexpected message : " + SafeEncoder.encode((byte[]) reply));
}
resultHandler.accept(reply);
} else {
throw new JedisException("Unknown message type: " + reply);
}
} while (!Thread.currentThread().isInterrupted() && isSubscribed());
// /* Invalidate instance since this thread is no longer listening */
// this.client = null;
}
}
View on GitHub (pinned to 6dac31d4c2)