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

  1. Do not run regular commands on the connection while the pub/sub loop is active; use a separate connection from the pool.
  2. On desync, close and reopen the connection (the stream state is unrecoverable).
  3. 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

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


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)