{"record":{"id":"bb51e420d133d475","repo":"ben-manes/caffeine","slug":"key-s-invalid-format-was-s-must-end-with-one-o","errorCode":null,"errorMessage":"key %s invalid format; was %s, must end with one of [dDhHmMsS]","messagePattern":"key (.+?) invalid format; was (.+?), must end with one of \\[dDhHmMsS\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java","lineNumber":350,"sourceCode":"  }\n\n  /** Returns a parsed {@link TimeUnit} value. */\n  @SuppressWarnings({\"ConstantValue\", \"StatementSwitchToExpressionSwitch\"})\n  static TimeUnit parseTimeUnit(String key, String value) {\n    requireArgument((value != null) && !value.isEmpty(), \"value of key %s omitted\", key);\n    @SuppressWarnings(\"null\")\n    char lastChar = Character.toLowerCase(value.charAt(value.length() - 1));\n    switch (lastChar) {\n      case 'd':\n        return TimeUnit.DAYS;\n      case 'h':\n        return TimeUnit.HOURS;\n      case 'm':\n        return TimeUnit.MINUTES;\n      case 's':\n        return TimeUnit.SECONDS;\n      default:\n        throw new IllegalArgumentException(String.format(US,\n            \"key %s invalid format; was %s, must end with one of [dDhHmMsS]\", key, value));\n    }\n  }\n\n  @Override\n  public boolean equals(@Nullable Object o) {\n    if (this == o) {\n      return true;\n    } else if (!(o instanceof CaffeineSpec)) {\n      return false;\n    }\n    var spec = (CaffeineSpec) o;\n    return Objects.equals(refreshAfterWrite, spec.refreshAfterWrite)\n        && Objects.equals(expireAfterAccess, spec.expireAfterAccess)\n        && Objects.equals(expireAfterWrite, spec.expireAfterWrite)\n        && Objects.equals(initialCapacity, spec.initialCapacity)\n        && Objects.equals(maximumWeight, spec.maximumWeight)\n        && Objects.equals(maximumSize, spec.maximumSize)","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/ben-manes/caffeine/blob/9da6581ee366aa63c51e0dc96692d02f9c29ccff/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java#L332-L368","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nCaffeine.from(\"expireAfterWrite=500ms\") # IllegalArgumentException: must end with [dDhHmMsS]\n\n# after\nCaffeine.from(\"expireAfterWrite=PT0.5S\") # 500ms via ISO-8601\n# or\nCaffeine.from(\"expireAfterWrite=1s\")","handlingStrategy":"validation","validationCode":"// Validate simple duration format before parsing:\nstatic boolean validSimpleDuration(String v) {\n  return v != null && v.length() >= 2 && \"dDhHmMsS\".indexOf(v.charAt(v.length() - 1)) >= 0\n      && v.substring(0, v.length() - 1).matches(\"[+-]?\\\\d+([_]\\\\d+)*\");\n}\nif (!validSimpleDuration(value)) {\n  // convert millis to a supported unit or ISO-8601\n  spec = \"expireAfterWrite=\" + Duration.ofMillis(millis)); // PT0.5S style\n}","typeGuard":null,"tryCatchPattern":"try {\n  return Caffeine.from(spec);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"[dDhHmMsS]\")) {\n    throw new ConfigException(\"Duration must use d/h/m/s suffix or ISO-8601: \" + spec, e);\n  }\n  throw e;\n}","preventionTips":["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()"],"tags":["caffeine","configuration","caffeine-spec","duration","time-unit"],"backgroundTag":null,"analyzedSha":"9da6581ee366aa63c51e0dc96692d02f9c29ccff","analyzedAt":"2026-08-14T14:45:28.147Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}