apache/pulsar · error · IllegalArgumentException

Invalid value %s for the position. Allowed values are [lates

Error message

Invalid value %s for the position. Allowed values are [latest, earliest]

What it means

ResetCursorData's String constructor only accepts the exact literals "latest" and "earliest" as a position; any other string cannot be mapped to a message position and triggers this IllegalArgumentException. Use the MessageId constructor for arbitrary positions.

Source

Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/ResetCursorData.java:65

    }

    public ResetCursorData(long ledgerId, long entryId, boolean isExcluded, Map<String, String> properties) {
        this.ledgerId = ledgerId;
        this.entryId = entryId;
        this.isExcluded = isExcluded;
        this.properties = properties;
    }

    // Private constructor used only for json deserialization
    private ResetCursorData(String position) {
        if ("latest".equals(position)) {
            this.ledgerId = Long.MAX_VALUE;
            this.entryId = Long.MAX_VALUE;
        } else if ("earliest".equals(position)) {
            this.ledgerId = -1;
            this.entryId = -1;
        } else {
            throw new IllegalArgumentException(
                    String.format("Invalid value %s for the position. Allowed values are [latest, earliest]",
                            position));
        }
    }

    public ResetCursorData(MessageId messageId) {
        MessageIdAdv messageIdAdv = (MessageIdAdv) messageId;
        this.ledgerId = messageIdAdv.getLedgerId();
        this.entryId = messageIdAdv.getEntryId();
        this.batchIndex = messageIdAdv.getBatchIndex();
        this.partitionIndex = messageIdAdv.getPartitionIndex();
        if (messageId instanceof TopicMessageId) {
            throw new IllegalArgumentException("Not supported operation on partitioned-topic");
        }
    }

}

View on GitHub (pinned to 820761864e)

Solutions

  1. Normalize input with position.trim().toLowerCase() before constructing ResetCursorData
  2. Map alternative synonyms (beginning->earliest, end->latest) before calling the constructor
  3. For arbitrary positions use new ResetCursorData(MessageId) instead of the string form

Example fix

// before
new ResetCursorData("LATEST"); // throws
// after
String p = input.trim().toLowerCase(Locale.ROOT);
new ResetCursorData(p); // "latest" or "earliest"
Defensive patterns

Strategy: validation

Validate before calling

if (!"latest".equals(position) && !"earliest".equals(position)) {
    throw new IllegalArgumentException("position must be 'latest' or 'earliest'");
}

Type guard

boolean isKnownPosition(String p) {
    return "latest".equals(p) || "earliest".equals(p);
}

Try / catch

try {
    resetCursorData = new ResetCursorData(position);
} catch (IllegalArgumentException e) {
    log.warn("Invalid reset position '{}', defaulting to latest", position);
    resetCursorData = new ResetCursorData("latest");
}

Prevention

When it happens

Trigger: Building ResetCursorData from user/CLI/API input where the position string is anything other than exactly "latest" or "earliest" (case differences like "Latest"/"LATEST", whitespace, or synonyms like "beginning"/"end").

Common situations: REST/admin tooling passing a user-supplied reset-position string through unvalidated; config files with mixed-case values; older code using "beginning"/"end" conventions instead of "earliest"/"latest".

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/8fabd623f0296976. Report an issue: GitHub.