{"record":{"id":"0ba3e265b5120f47","repo":"redis/jedis","slug":"unexpected-message-reply-0ba3e2","errorCode":null,"errorMessage":"Unexpected message : <reply>","messagePattern":"Unexpected message : <reply>","errorType":"exception","errorClass":"JedisException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/JedisShardedPubSubBase.java","lineNumber":109,"sourceCode":"          onSSubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(SUNSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          onSUnsubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(SMESSAGE.getRaw(), resp)) {\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final byte[] bmesg = (byte[]) listReply.get(2);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          final T enmesg = (bmesg == null) ? null : encode(bmesg);\n          onSMessage(enchannel, enmesg);\n        } else {\n          throw new JedisException(\"Unknown message type: \" + firstObj);\n        }\n      } else if (reply instanceof byte[]) {\n        Consumer<Object> resultHandler = authenticator.resultHandler.poll();\n        if (resultHandler == null) {\n          throw new JedisException(\"Unexpected message : \" + SafeEncoder.encode((byte[]) reply));\n        }\n        resultHandler.accept(reply);\n      } else {\n        throw new JedisException(\"Unknown message type: \" + reply);\n      }\n    } while (!Thread.currentThread().isInterrupted() && isSubscribed());\n\n//    /* Invalidate instance since this thread is no longer listening */\n//    this.client = null;\n  }\n}\n","sourceCodeStart":91,"sourceCodeEnd":121,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/JedisShardedPubSubBase.java#L91-L121","documentation":"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>).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npubSub.ssubscribe(j, ch);\notherThread.set(\"k\", \"v\"); // reply lands in pub/sub loop -> Unexpected message\n// after\npubSub.ssubscribe(j, ch);   // all other commands on separate connections\nj2.set(\"k\", \"v\");          // use a different Jedis instance","handlingStrategy":"try-catch","validationCode":"// ensure no concurrent command can target the pub/sub connection\nassert !jedis.isInMulti();","typeGuard":"boolean isCommandAck(Object reply) { return reply instanceof byte[]; }","tryCatchPattern":"try {\n  shardedPubSub.proceed(jedis, channel);\n} catch (JedisException e) {\n  if (e.getMessage().startsWith(\"Unexpected message\")) {\n    jedis.close(); // re-create connection and subscription\n  } else throw e;\n}","preventionTips":["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."],"tags":["pubsub","thread-safety","redis"],"backgroundTag":"unexpected-response-shape","analyzedSha":"6dac31d4c224fb3257c216f3985340c6f500cdcb","analyzedAt":"2026-09-08T04:55:01.204Z","contentChangedAt":"2026-09-08T04:55:01.204Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}