ben-manes/caffeine · error · IllegalArgumentException

key %s invalid format; was %s, but the duration cannot be pa

Error message

key %s invalid format; was %s, but the duration cannot be parsed

What it means

When a duration option value contains 'T' (ISO-8601 marker), CaffeineSpec.parseIsoDuration delegates to Duration.parse and throws IllegalArgumentException("key %s invalid format; was %s, but the duration cannot be parsed") on DateTimeParseException. The spec string's duration must be a valid ISO-8601 duration (e.g. PT10M) or a simple form like 10m.

Source

Thrown at caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java:322

  static Duration parseDuration(String key, @Nullable String value) {
    requireArgument((value != null) && !value.isEmpty(), "value of key %s omitted", key);
    requireNonNull(value);

    boolean isIsoFormat = value.contains("p") || value.contains("P");
    Duration duration = isIsoFormat
        ? parseIsoDuration(key, value)
        : parseSimpleDuration(key, value);
    requireArgument(!duration.isNegative(),
        "key %s invalid format; was %s, but the duration cannot be negative", key, value);
    return duration;
  }

  /** Returns a parsed duration using the ISO-8601 format. */
  static Duration parseIsoDuration(String key, String value) {
    try {
      return Duration.parse(value);
    } catch (DateTimeParseException e) {
      throw new IllegalArgumentException(String.format(US,
          "key %s invalid format; was %s, but the duration cannot be parsed", key, value), e);
    }
  }

  /** Returns a parsed duration using the simple time unit format. */
  static Duration parseSimpleDuration(String key, String value) {
    long duration = parseLong(key, value.substring(0, value.length() - 1));
    TimeUnit unit = parseTimeUnit(key, value);
    return Duration.ofNanos(unit.toNanos(duration));
  }

  /** Returns a parsed {@link TimeUnit} value. */
  @SuppressWarnings({"ConstantValue", "StatementSwitchToExpressionSwitch"})
  static TimeUnit parseTimeUnit(String key, String value) {
    requireArgument((value != null) && !value.isEmpty(), "value of key %s omitted", key);
    @SuppressWarnings("null")
    char lastChar = Character.toLowerCase(value.charAt(value.length() - 1));
    switch (lastChar) {

View on GitHub (pinned to 9da6581ee3)

Solutions

  1. Use a complete ISO-8601 duration: expireAfterWrite=PT10M30S (the T separates date from time parts)
  2. Or use the simple format with a unit suffix: expireAfterWrite=10m
  3. When generating values programmatically, build them from Duration.toString() to guarantee validity

Example fix

# before
Caffeine.from("expireAfterWrite=P10M")  // parsed as 10 months, or rejected if truncated
Caffeine.from("expireAfterWrite=PT")    // IllegalArgumentException: cannot be parsed

# after
Caffeine.from("expireAfterWrite=PT10M") # 10 minutes
# or
Caffeine.from("expireAfterWrite=10m")
Defensive patterns

Strategy: validation

Validate before calling

// Validate ISO durations before building the spec:
static String isoDuration(String key, Duration d) {
  if (d.isNegative()) throw new ConfigException(key + " must not be negative");
  return key + "=" + d; // Duration.toString() is always valid ISO-8601
}
String spec = isoDuration("expireAfterWrite", Duration.ofMinutes(10));

Try / catch

try {
  return Caffeine.from(spec);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().contains("duration cannot be parsed")) {
    // Fall back to builder API with a safe default
    return Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(10));
  }
  throw e;
}

Prevention

When it happens

Trigger: Caffeine.from("expireAfterWrite=PT") or "expireAfterWrite=P1D30M" (malformed ISO-8601); using ISO syntax without the T for time components, e.g. "P10M" intended as ten minutes (that is ten months in ISO); partial values like "expireAfterAccess=T30s".

Common situations: Mixing simple-format habits into ISO-8601 strings; generating spec strings from templates that drop required components; assuming 'P10M' means minutes rather than months.

Related errors


AI-assisted analysis of ben-manes/caffeine@9da6581ee3 (2026-08-14). Data as JSON: /api/errors/17afa7f2ea038cf7. Report an issue: GitHub.