redis/jedis · error · JedisDataException

Unexpected response

Error message

Unexpected response: ${response}

What it means

During exec(), MultiDbTransaction issues MULTI on the acquired connection and expects an 'OK' reply. If the server returns anything else (a Redis error like WRONGTYPE-encoded response, a proxy-injected error, or an unexpected object), it throws JedisDataException("Unexpected response: <reply>") and aborts before sending the queued commands.

Solutions

  1. Discard the transaction, obtain a fresh transaction/connection, and retry the whole multi()/commands/exec() cycle
  2. Check server/proxy support for MULTI on the endpoint being used
  3. Catch JedisDataException in exec() paths and log the raw reply to identify the server-side cause

Example fix

// before
List<Object> res = transaction.exec(); // may throw on bad MULTI reply
// after
try {
  List<Object> res = transaction.exec();
} catch (JedisDataException e) {
  transaction.close();
  transaction = client.transaction();
  transaction.multi();
  // re-issue commands and exec again
}
Defensive patterns

Strategy: retry

Try / catch

try { res = t.exec(); } catch (JedisDataException e) { t.close(); /* rebuild transaction and retry once */ }

Prevention

When it happens

Trigger: The server replies to MULTI with something other than +OK — e.g. the connection is actually in an error state, a proxy/cluster intercepted MULTI, the connection was switched to a different database mid-transaction, or a previous error left the protocol stream misaligned.

Common situations: Failover replaced the underlying connection mid-transaction; running against a proxy (e.g. Redis Enterprise REST-managed endpoints) that rejects MULTI; stale connection after a network blip.

Related errors


AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08). Data as JSON: /api/errors/e3a7dd01b996f0d2. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java:154

    }
  }

  @Override
  public final List<Object> exec() {
    if (!inMulti) {
      throw new IllegalStateException("EXEC without MULTI");
    }

    boolean serverInMultiMode = false;
    try {
      Connection conn = acquireConnection();

      Object multiReply = conn
          .executeCommand(new CommandObject<>(new CommandArguments(MULTI), NO_OP_BUILDER));
      if (!bytesEquals(OK_IN_BYTES, multiReply)) {
        Object response = multiReply instanceof byte[] ? SafeEncoder.encode((byte[]) multiReply)
            : multiReply;
        throw new JedisDataException("Unexpected response: " + response);
      }

      serverInMultiMode = true;

      commands.forEach((command) -> conn.sendCommand(command.getKey()));
      // following connection.getMany(int) flushes anyway, so no flush here.

      // server replies QUEUED OR ERROR for each buffered command
      List<Object> queuedCmdResponses = conn.getMany(commands.size());

      if (!connectionSupplier.isActiveDatabase(initialDatabase)) {
        JedisException dbSwitchException = new JedisException(
            "Active database has changed since transaction started");
        try {
          conn.executeCommand(new CommandArguments(DISCARD));
          serverInMultiMode = false;
        } catch (Exception e) {
          dbSwitchException.addSuppressed(e);

View on GitHub (pinned to 6dac31d4c2)