Skip to content

Redis PING command throws ClassCastException when client is in SUBSCRIBE mode #4162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sumitswami78 opened this issue May 7, 2025 · 5 comments
Labels

Comments

@sumitswami78
Copy link

Problem Description

According to the official Redis documentation, the PING command is allowed while the client is in SUBSCRIBE mode, and it should return a PONG response. This applies to both RESP2 and RESP3 protocols.
supporting Link

However, when using the Jedis library, calling ping() on a client that is currently in SUBSCRIBE mode results in a ClassCastException:

java.lang.ClassCastException: java.util.ArrayList cannot be cast to byte[]

Root Cause:

The issue appears to be in the implementation of the ping() method which can be found in the File Jedis.java , which internally calls getStatusCodeReply():

@Override
public String ping() {
  checkIsInMultiOrPipeline();
  connection.sendCommand(Command.PING);
  return connection.getStatusCodeReply();
}

The getStatusCodeReply() method is implemented as follows, following implementation can be found in Connection.java file:

public String getStatusCodeReply() {
  flush();
  final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
  if (null == resp) {
    return null;
  } else {
    return encode(resp);
  }
}

The readProtocolWithCheckingBroken() method is expected to return an Object as per the given implementation which can be found in Connection.java File. It returns an ArrayList when the client is in SUBSCRIBE mode, causing the cast to byte[] to fail with a ClassCastException. Proof for the Same which shows when in SUBSCRIBE Mode in redis-cli, the PING command returns an ArrayList consisting of an empty String and the message PONG

Image

Expected Behaviour:

The ping() method should return "PONG" when the client is in SUBSCRIBE mode, just like the Redis CLI or any compliant Redis client implementation.

Steps to Reproduce:

  1. Connect to Redis using Jedis.
  2. Call subscribe() to enter subscription mode.
  3. While subscribed, call ping().
  4. Observe the exception.

Jedis version: 5.0.2
Redis version: 7.2.7

@ggivo ggivo added the waiting-for-triage Still needs to be triaged label May 7, 2025
@ggivo
Copy link
Collaborator

ggivo commented May 7, 2025

Hi, Thanks for the detailed report.

I tried to reproduce the ClassCastException with Jedis itself, but did not succeed.
Started Jedis client and published "SEND_PING" message from redis-cli client to the same channel mychannel which triggers ping command invocation. Then I got the onPong() invoked correctly.

Tested with
Jedis: 5.0.2 and 6.0.0
Redis: 8.0.0

Note: I have verified the array output for PING is returned when subscribed & using RESP2

import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.RedisProtocol;

class Scratch {
  public static void main(String[] args) {

    try {
      Jedis jedis = new Jedis("localhost", 6379, DefaultJedisClientConfig.builder().protocol(
          RedisProtocol.RESP2).build());
      jedis.subscribe(new JedisPubSub() {
        @Override
        public void onMessage(String channel, String message) {
          if (message.equals("SEND_PING")) {
            ping();
          }
          System.out.println("Received message: " + message);
        }

        @Override
        public void onPong(String pattern) {
          System.out.println("Received PONG: " + pattern);
        }

      }, "mychannel");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}
Connected to the target VM, address: '127.0.0.1:50126', transport: 'socket'
Received message: SEND_PING
Received PONG: 

Also took a look at the code, and it seems array output is kind of expected and already handled (supportingLink)

Could you provide an example code on how to reproduce it?

Hope it helps!

@ggivo ggivo added could not reproduce waiting-for-feedback We need additional information before we can continue and removed waiting-for-triage Still needs to be triaged labels May 7, 2025
@sumitswami78
Copy link
Author

sumitswami78 commented May 7, 2025

Hey @ggivo Thanks for the super Fast Response. In the Code snippet attached above you are trying to just call the ping() function instead if you try to call the ping function on the jedis instance you create above you will be able to reproduce it. Adding the Updated Code snippet which is still giving me

java.lang.ClassCastException

Here is the updated Code snippet with the only change being ping() replaced with jedis.ping() in the onMessage Callback

import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.RedisProtocol;

class Scratch {
  public static void main(String[] args) {

    try {
      Jedis jedis = new Jedis("localhost", 6379, DefaultJedisClientConfig.builder().protocol(
          RedisProtocol.RESP2).build());
      jedis.subscribe(new JedisPubSub() {
        @Override
        public void onMessage(String channel, String message) {
          if (message.equals("SEND_PING")) {
            jedis.ping();
          }
          System.out.println("Received message: " + message);
        }

        @Override
        public void onPong(String pattern) {
          System.out.println("Received PONG: " + pattern);
        }

      }, "mychannel");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Complete Stack Trace of the Exception

java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class [B (java.util.ArrayList and [B are in module java.base of loader 'bootstrap')
	at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:268)
	at redis.clients.jedis.Jedis.ping(Jedis.java:339)
	at Scratch$1.onMessage(Main.java:17)
	at Scratch$1.onMessage(Main.java:13)
	at redis.clients.jedis.JedisPubSubBase.process(JedisPubSubBase.java:139)
	at redis.clients.jedis.JedisPubSubBase.proceed(JedisPubSubBase.java:92)
	at redis.clients.jedis.Jedis.subscribe(Jedis.java:7941)
	at Scratch.main(Main.java:13)

@ggivo
Copy link
Collaborator

ggivo commented May 7, 2025

Thanks, I see.

From PING docs

If the client is subscribed to a channel or a pattern, it will instead return a multi-bulk with a "pong" in the first position and an empty bulk in the second position, unless an argument is provided, in which case it returns a copy of the argument.

Testing locally seems this applies to RESP2 (with'RESP3, I always get either PONG or the provided argument value).

@sumitswami78
For RESP2 allowed commands when subscribed are available and handled by JedisPubSub, any reason not to use JedisPubSub.ping()?

@ggivo ggivo added bug and removed could not reproduce waiting-for-feedback We need additional information before we can continue labels May 7, 2025
@sumitswami78
Copy link
Author

sumitswami78 commented May 8, 2025

Yeah, with RESP3 there seems to be no issue.

No particular reason to not use JedisPubSub.ping() , was trying out different things for the useCase i am trying to solve for and came across this issue.

One generic Doubt here, if jedis.ping() (with RESP3) or JedisPubSub.ping() return PONG, does it always mean the subscriber connection is up and running fine or are there any details which i am missing? if yes pls point me towards the suitable Documentation.

Thanks @ggivo

@ggivo
Copy link
Collaborator

ggivo commented May 8, 2025

One generic Doubt here, if jedis.ping() (with RESP3) or JedisPubSub.ping() return PONG, does it always mean the subscriber connection is up and running fine or are there any details which i am missing? if yes pls point me towards the suitable Documentation.

PING is a regular command used to test the connection, and if it makes it to the Redis server and you get a PONG, it means the connection is up and running.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants