apache/pulsar · error · IllegalArgumentException

Invalid time unit

Error message

Invalid time unit '<lastChar>'

What it means

RelativeTimeUtil.parseRelativeTimeInSeconds parses strings like '60s', '5m', '2h', '7d', '2w', '1y' into seconds. The last character is taken as the time unit; if it is alphabetic but not one of s/m/h/d/w/y (case-insensitive), the parser throws this IllegalArgumentException. Note that months are intentionally unsupported, so 'M' and other letters like 'x' or 'z' are rejected.

Solutions

  1. Fix the input string to use only supported units: s, m, h, d, w, or y (e.g. '30m' instead of '30M').
  2. Express months as days or weeks (e.g. '30d') since months are not a supported unit.
  3. Strip surrounding whitespace/typos so the string ends with a valid single-letter unit.
  4. Validate the unit character before calling the parser in user-facing code.

Example fix

// before
long seconds = RelativeTimeUtil.parseRelativeTimeInSeconds("30M");
// after
long seconds = RelativeTimeUtil.parseRelativeTimeInSeconds("30d");
Defensive patterns

Strategy: validation

Validate before calling

private static final java.util.Set<Character> VALID_UNITS = java.util.Set.of('s','m','h','d','w','y');
static long safeParseRelativeTime(String input) {
    String trimmed = input.trim();
    if (trimmed.isEmpty()) throw new IllegalArgumentException("expiry time cannot be empty");
    char last = Character.toLowerCase(trimmed.charAt(trimmed.length() - 1));
    if (Character.isAlphabetic(last) && !VALID_UNITS.contains(last)) {
        throw new IllegalArgumentException("Invalid time unit '" + last + "'; use s, m, h, d, w, or y");
    }
    return RelativeTimeUtil.parseRelativeTimeInSeconds(trimmed);
}

Type guard

static boolean isValidRelativeTimeString(String s) {
    if (s == null || s.isEmpty()) return false;
    char last = Character.toLowerCase(s.charAt(s.length() - 1));
    if (!Character.isAlphabetic(last)) return true; // no unit -> seconds
    return "smdwy".indexOf(last) >= 0;
}

Try / catch

try {
    long seconds = RelativeTimeUtil.parseRelativeTimeInSeconds(userInput);
} catch (IllegalArgumentException e) {
    LOG.error("Invalid relative time '{}' (units: s, m, h, d, w, y)", userInput, e);
    throw new ConfigurationException("Invalid time value: " + userInput, e);
}

Prevention

When it happens

Trigger: Calling parseRelativeTimeInSeconds with a string ending in a letter other than s, m, h, d, w, or y (e.g. '30M' for minutes uppercase-collision concerns or months, '1mo', '10x', '5min'). Any user/config supplied relative time value (e.g. TTL, retention, expiry settings) with an unrecognized unit suffix.

Common situations: Users write '30M' or '1mo' expecting months or minutes; config files carry units like 'ms', 'min', or 'sec' carried over from other tools; typo in unit suffix ('2hh'); locale or copy-paste issues introduce odd trailing characters.

Related errors


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

Appendix: source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/RelativeTimeUtil.java:65

        long duration = Long.parseLong(relativeTime.substring(0, lastIndex));

        switch (timeUnit) {
        case 's':
            return duration;
        case 'm':
            return TimeUnit.MINUTES.toSeconds(duration);
        case 'h':
            return TimeUnit.HOURS.toSeconds(duration);
        case 'd':
            return TimeUnit.DAYS.toSeconds(duration);
        case 'w':
            return 7 * TimeUnit.DAYS.toSeconds(duration);
        // No unit for months
        case 'y':
            return 365 * TimeUnit.DAYS.toSeconds(duration);
        default:
            throw new IllegalArgumentException("Invalid time unit '" + lastChar + "'");
        }
    }

    /**
     * Convert nanoseconds to seconds and keep three decimal places.
     * @param ns
     * @return seconds
     */
    public static double nsToSeconds(long ns) {
        double seconds = (double) ns / 1_000_000_000;
        BigDecimal bd = new BigDecimal(seconds);
        return bd.setScale(3, RoundingMode.HALF_UP).doubleValue();
    }
}

View on GitHub (pinned to 820761864e)