ben-manes/caffeine · error · IllegalArgumentException
key %s invalid format; was %s, must end with one of [dDhHmMs
Error message
key %s invalid format; was %s, must end with one of [dDhHmMsS]
What it means
CaffeineSpec.parseTimeUnit inspects the last character of a simple-format duration and throws IllegalArgumentException("key %s invalid format; was %s, must end with one of [dDhHmMsS]") unless it is d/h/m/s (case-insensitive). Only day, hour, minute, and second units are supported in the simple format; sub-second durations require ISO-8601.
Source
Thrown at caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java:350
}
/** 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) {
case 'd':
return TimeUnit.DAYS;
case 'h':
return TimeUnit.HOURS;
case 'm':
return TimeUnit.MINUTES;
case 's':
return TimeUnit.SECONDS;
default:
throw new IllegalArgumentException(String.format(US,
"key %s invalid format; was %s, must end with one of [dDhHmMsS]", key, value));
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
} else if (!(o instanceof CaffeineSpec)) {
return false;
}
var spec = (CaffeineSpec) o;
return Objects.equals(refreshAfterWrite, spec.refreshAfterWrite)
&& Objects.equals(expireAfterAccess, spec.expireAfterAccess)
&& Objects.equals(expireAfterWrite, spec.expireAfterWrite)
&& Objects.equals(initialCapacity, spec.initialCapacity)
&& Objects.equals(maximumWeight, spec.maximumWeight)
&& Objects.equals(maximumSize, spec.maximumSize)View on GitHub (pinned to 9da6581ee3)
Solutions
- Express the duration with an allowed unit: d/h/m/s, e.g. expireAfterWrite=30s, 10m, 2h, 1d
- For sub-second durations use ISO-8601: expireAfterWrite=PT0.5S
- When converting from millis, compute the best whole unit programmatically before building the spec
Example fix
# before
Caffeine.from("expireAfterWrite=500ms") # IllegalArgumentException: must end with [dDhHmMsS]
# after
Caffeine.from("expireAfterWrite=PT0.5S") # 500ms via ISO-8601
# or
Caffeine.from("expireAfterWrite=1s") Defensive patterns
Strategy: validation
Validate before calling
// Validate simple duration format before parsing:
static boolean validSimpleDuration(String v) {
return v != null && v.length() >= 2 && "dDhHmMsS".indexOf(v.charAt(v.length() - 1)) >= 0
&& v.substring(0, v.length() - 1).matches("[+-]?\\d+([_]\\d+)*");
}
if (!validSimpleDuration(value)) {
// convert millis to a supported unit or ISO-8601
spec = "expireAfterWrite=" + Duration.ofMillis(millis)); // PT0.5S style
} Try / catch
try {
return Caffeine.from(spec);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("[dDhHmMsS]")) {
throw new ConfigException("Duration must use d/h/m/s suffix or ISO-8601: " + spec, e);
}
throw e;
} Prevention
- The simple format supports only d/h/m/s — use ISO-8601 (PT0.5S) for sub-second values
- Never write bare numbers (30) or unsupported suffixes (30ms, 30min) in specs
- Convert millisecond configs programmatically: Duration.ofMillis(ms).toString()
When it happens
Trigger: Caffeine.from("expireAfterWrite=500ms") (milliseconds not supported in simple format); a bare number "expireAfterWrite=30"; a typo'd suffix like "30min" or "1hr"; an empty value after key stripping.
Common situations: Porting configs from libraries that accept ms/ns units; expecting millis because other tools use them; forgetting that ISO-8601 (PT0.5S) is needed for sub-second expiry.
Related errors
- key %s invalid format; was %s, but the duration cannot be pa
- Invalid option
- key %s value was set to %s, must be an integer
- key %s value was set to %s, must be a long
- throw new ExceptionInInitializerError(e)
AI-assisted analysis of ben-manes/caffeine@9da6581ee3 (2026-08-14).
Data as JSON: /api/errors/bb51e420d133d475.
Report an issue: GitHub.