{"record":{"id":"ea8d9a4183bc64ac","repo":"apache/iceberg","slug":"cannot-parse-to-a-duration-string-value-s-s","errorCode":null,"errorMessage":"Cannot parse to a duration string value: %s: %s","messagePattern":"Cannot parse to a duration string value: (.+?): (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/util/JsonUtil.java","lineNumber":201,"sourceCode":"      return null;\n    }\n    JsonNode pNode = node.get(property);\n    if (pNode != null && pNode.isNull()) {\n      return null;\n    }\n    return getString(property, node);\n  }\n\n  public static String getDurationStringOrNull(String property, JsonNode node) {\n    String value = getStringOrNull(property, node);\n    if (value == null) {\n      return null;\n    }\n\n    try {\n      java.time.Duration.parse(value);\n    } catch (RuntimeException e) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Cannot parse to a duration string value: %s: %s\", property, node.get(property)),\n          e);\n    }\n\n    return value;\n  }\n\n  public static ByteBuffer getByteBufferOrNull(String property, JsonNode node) {\n    if (!node.has(property) || node.get(property).isNull()) {\n      return null;\n    }\n\n    JsonNode pNode = node.get(property);\n    Preconditions.checkArgument(\n        pNode.isTextual(), \"Cannot parse byte buffer from non-text value: %s: %s\", property, pNode);\n    return ByteBuffer.wrap(\n        BaseEncoding.base16().decode(pNode.textValue().toUpperCase(Locale.ROOT)));","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/util/JsonUtil.java#L183-L219","documentation":"JsonUtil.getDurationStringOrNull(property, node) verifies that the given JSON field is a string parseable by java.time.Duration.parse. If the value is present but not a valid ISO-8601 duration, it throws IllegalArgumentException naming the property and offending value.","triggerScenarios":"Passing a config field like 'history.expire.max-snapshot-age-ms'-style durations as '5 minutes' or '5' instead of the ISO-8601 form 'PT5M' in JSON table metadata or catalog config.","commonSituations":"Hand-edited JSON config files using human-readable duration strings; tooling that writes durations without the required PT prefix; timezone/locale-specific formatting leaking into config.","solutions":["Change the value to a valid ISO-8601 duration, e.g. 'PT10M' for 10 minutes or 'PT1H30S'","Use java.time.Duration.toString() or Duration.of(...).toString() when generating config JSON","Validate the string with Duration.parse() before writing it into the config"],"exampleFix":"// before\n{ \"history.expire.max-snapshot-age\": \"10 minutes\" }\n// after\n{ \"history.expire.max-snapshot-age\": \"PT10M\" }","handlingStrategy":"validation","validationCode":"String v = jsonNode.get(\"history.expire.max-snapshot-age\").asText();\njava.time.Duration.parse(v); // throws DateTimeParseException if invalid","typeGuard":"boolean isIsoDuration(String s) { try { java.time.Duration.parse(s); return true; } catch (Exception e) { return false; } }","tryCatchPattern":"try { Duration d = JsonUtil.getDurationStringOrNull(prop, node) ...; } catch (IllegalArgumentException e) { /* fix config value to ISO-8601 */ }","preventionTips":["Always write durations as ISO-8601 strings (PT10M, PT1H)","Use Duration.toString() when serializing to JSON","Add schema/config validation on ingestion"],"tags":["json","duration","parsing","config"],"backgroundTag":"invalid-duration-format","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}