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

  1. Do not issue commands on the pub/sub connection while the process() loop is running.
  2. Create a fresh JedisPubSubBase per subscription session; don't reuse after unsubscribe.
  3. Synchronize subscribe/unsubscribe calls so handlers are registered before their acks arrive.
  4. 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

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


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)