redis/jedis · error · JedisException
Unexpected message
Error message
Unexpected message : <reply>
What it means
When a reply arrives as a raw byte[] (rather than a list), process() assumes it is the response to an earlier one-shot command queued via a resultHandler. If the handler queue is empty, there is no one expecting this reply, so Jedis throws this JedisException ('Unexpected message : ...') (JedisPubSubBase.java:194).
Solutions
- Do not run regular commands on the connection while the pub/sub loop is active; use a separate connection from the pool.
- On desync, close and reopen the connection (the stream state is unrecoverable).
- Upgrade Jedis if you mix RESP3 push messages, which changed reply dispatch handling.
Example fix
// before
jedis.set("k", "v"); // on subscribed connection
pubSub.proceed(jedis, "ch"); // stray SET reply -> Unexpected message
// after
try (Jedis other = pool.getResource()) { other.set("k", "v"); }
pubSub.proceed(jedis, "ch"); Defensive patterns
Strategy: try-catch
Validate before calling
// do not run regular commands on a connection inside a subscribe loop; // acquire a separate connection from the pool for one-shot commands
Try / catch
try {
pubSub.proceed(jedis, channel);
} catch (JedisException e) {
if (e.getMessage().startsWith("Unexpected message :")) {
// protocol desync: close and reopen connection
jedis.close();
jedis = pool.getResource();
pubSub.proceed(jedis, channel);
}
} Prevention
- Keep one-shot commands off the pub/sub connection; use pool.getResource() for them.
- After any exception in the loop, discard the connection — buffer state may be desynced.
- Avoid custom/proxy layers that inject extra replies into RESP streams.
When it happens
Trigger: A bare byte[] reply is read in the pub/sub loop with no pending resultHandler — e.g. a late/stray command reply, a ping response after its consumer was already consumed, or protocol desync between issued commands and read replies.
Common situations: Calling commands on the subscribed connection asynchronously while the loop runs; prior exceptions skipped replies leaving stale bytes in the buffer; proxies injecting extra responses.
Related errors
- Blocking pub/sub operations are not supported on…
- Unknown message type
- Unknown message type
- Unknown message type
- Failed to create socket.
AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08).
Data as JSON: /api/errors/af880f2028224f5b.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/redis/clients/jedis/JedisPubSubBase.java:194
final byte[] bpattern = (byte[]) listReply.get(1);
final T enpattern = (bpattern == null) ? null : encode(bpattern);
onPSubscribe(enpattern, subscribedChannels);
} else if (Arrays.equals(PUNSUBSCRIBE.getRaw(), resp)) {
subscribedChannels = ((Long) listReply.get(2)).intValue();
final byte[] bpattern = (byte[]) listReply.get(1);
final T enpattern = (bpattern == null) ? null : encode(bpattern);
onPUnsubscribe(enpattern, subscribedChannels);
} else if (Arrays.equals(PONG.getRaw(), resp)) {
final byte[] bpattern = (byte[]) listReply.get(1);
final T enpattern = (bpattern == null) ? null : encode(bpattern);
onPong(enpattern);
} 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;
}
private void processPingReply(Object reply) {
byte[] resp = (byte[]) reply;
if ("PONG".equals(SafeEncoder.encode(resp))) {
onPong(null);
} else {
onPong(encode(resp));
}View on GitHub (pinned to 6dac31d4c2)