prestodb/presto · error · RedisProviderSerdeException

Error decoding key planHash which was of type String

Error message

Error decoding key planHash which was of type String

What it means

HistoricalStatisticsSerde.decodeKey can only decode a UTF-8 String key when the ByteBuffer is backed by a heap array (hasArray() == true). If the buffer is a direct/off-heap or otherwise non-array-backed ByteBuffer, it throws RedisProviderSerdeException because the UTF_8.decode path cannot be applied directly.

Source

Thrown at redis-hbo-provider/src/main/java/com/facebook/presto/statistic/HistoricalStatisticsSerde.java:46

import java.nio.charset.StandardCharsets;

/**
 * Redis codec implementation for string keys and HistoricalPlanStatistics values.
 */
public class HistoricalStatisticsSerde
        implements RedisCodec<String, HistoricalPlanStatistics>
{
    private static final int ESTIMATED_BUFFER_SIZE_BYTES = 100 * 1024;
    private final ThriftCodecManager thriftCodecManager = new ThriftCodecManager();

    @Override
    public String decodeKey(ByteBuffer bytes)
    {
        if (bytes.hasArray()) {
            return StandardCharsets.UTF_8.decode(bytes).toString();
        }
        else {
            throw new RedisProviderSerdeException("Error decoding key planHash which was of type String");
        }
    }

    @Override
    public ByteBuffer encodeKey(String key)
    {
        return ByteBuffer.wrap(key.getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public ByteBuffer encodeValue(HistoricalPlanStatistics historicalPlanStatistics)
    {
        ThriftCodec<HistoricalPlanStatistics> writeCodec = thriftCodecManager.getCodec(HistoricalPlanStatistics.class);
        SliceOutput dynamicSliceOutput = new DynamicSliceOutput(ESTIMATED_BUFFER_SIZE_BYTES);
        try {
            ThriftProtocolUtils.write(historicalPlanStatistics, writeCodec, Protocol.BINARY, dynamicSliceOutput);
            return ByteBuffer.wrap(dynamicSliceOutput.slice().getBytes());
        }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Add a fallback that copies direct buffer contents into a heap array before UTF-8 decoding: ByteBuffer.wrap(new byte[bytes.remaining()]).put(bytes.duplicate())
  2. Ensure the producer of the key ByteBuffer allocates on-heap (ByteBuffer.allocate / wrap)
  3. Update the Redis provider/serde integration so keys are passed as array-backed buffers

Example fix

// before
if (bytes.hasArray()) {
    return StandardCharsets.UTF_8.decode(bytes).toString();
}
throw new RedisProviderSerdeException("Error decoding key planHash which was of type String");
// after
ByteBuffer dup = bytes.duplicate();
byte[] array = new byte[dup.remaining()];
dup.get(array);
return new String(array, StandardCharsets.UTF_8);
Defensive patterns

Strategy: try-catch

Validate before calling

boolean isDecodable(ByteBuffer buf) {
    return buf != null && buf.hasArray();
}

Type guard

boolean isHeapByteBuffer(ByteBuffer buf) {
    return buf != null && buf.hasArray();
}

Try / catch

try {
    key = serde.decodeKey(buffer);
} catch (RedisProviderSerdeException e) {
    ByteBuffer dup = buffer.duplicate();
    byte[] arr = new byte[dup.remaining()];
    dup.get(arr);
    key = new String(arr, StandardCharsets.UTF_8);
}

Prevention

When it happens

Trigger: decodeKey is called with a direct ByteBuffer (allocated via allocateDirect) or a sliced/duplicated buffer with no accessible backing array, e.g. a key read from the Redis client into direct memory.

Common situations: Redis provider returns direct ByteBuffers for performance; a custom serde pipeline swaps heap buffers for direct ones; a library upgrade changes how ByteBuffers are produced upstream.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/9b16d55079b666b4. Report an issue: GitHub.