{"record":{"id":"a047d0338ac7e2d9","repo":"eclipse-vertx/vert.x","slug":"ttl-must-be-positive-ttl","errorCode":null,"errorMessage":"ttl must be positive: ${ttl}","messagePattern":"ttl must be positive: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"vertx-core/src/main/java/io/vertx/core/shareddata/impl/LocalAsyncMapImpl.java","lineNumber":259,"sourceCode":"    }\n  }\n\n  private static class Holder<V> {\n    final V value;\n    final long timerId;\n    final long ttl;\n    final long timestamp;\n\n    Holder(V value) {\n      Objects.requireNonNull(value);\n      this.value = value;\n      timestamp = ttl = timerId = 0;\n    }\n\n    Holder(V value, long timerId, long ttl, long timestamp) {\n      Objects.requireNonNull(value);\n      if (ttl < 1) {\n        throw new IllegalArgumentException(\"ttl must be positive: \" + ttl);\n      }\n      this.value = value;\n      this.timerId = timerId;\n      this.ttl = ttl;\n      this.timestamp = timestamp;\n    }\n\n    boolean expires() {\n      return ttl > 0;\n    }\n\n    boolean hasNotExpired() {\n      return !expires() || MILLISECONDS.convert(System.nanoTime() - timestamp, NANOSECONDS) < ttl;\n    }\n\n    @Override\n    public String toString() {\n      return \"Holder{\" + \"value=\" + value + \", timerId=\" + timerId + \", ttl=\" + ttl + \", timestamp=\" + timestamp + '}';","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/shareddata/impl/LocalAsyncMapImpl.java#L241-L277","documentation":"LocalAsyncMapImpl.Holder wraps values that carry a time-to-live. The constructor requires ttl >= 1 because a zero/negative TTL cannot produce a valid expiration timer; it throws IllegalArgumentException otherwise.","triggerScenarios":"Calling LocalAsyncMap.put(k, v, ttl) with ttl <= 0, or constructing a Holder directly with a non-positive ttl.","commonSituations":"A computed TTL comes from configuration that defaulted to 0 (e.g. missing 'cache.ttl' property) or from a subtraction/calculation that produced 0 or a negative value.","solutions":["Pass a ttl >= 1 (milliseconds) when calling put with TTL.","Validate/clamp the TTL before calling: if (ttl <= 0) throw or use a sane default.","Fix the configuration source so the TTL property is set to a positive number."],"exampleFix":"// before\nmap.put(\"key\", value, ttlFromConfig); // ttlFromConfig == 0 -> IllegalArgumentException\n// after\nif (ttlFromConfig <= 0) ttlFromConfig = 60_000L;\nmap.put(\"key\", value, ttlFromConfig);","handlingStrategy":"validation","validationCode":"if (ttl <= 0) throw new IllegalArgumentException(\"ttl must be positive, got \" + ttl);\nmap.put(key, value, ttl);","typeGuard":null,"tryCatchPattern":"try {\n  map.put(key, value, ttl);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().startsWith(\"ttl must be positive\")) {\n    map.put(key, value, defaultTtl);\n  } else throw e;\n}","preventionTips":["Validate TTL values loaded from config before use (must be >= 1).","Use a helper method that clamps or defaults non-positive TTLs.","Keep TTL units documented (milliseconds in Vert.x) to avoid zero conversions."],"tags":["shared-data","async-map","ttl","illegal-argument"],"backgroundTag":"value-out-of-range","analyzedSha":"fb308bd8c3f12c79f4ae89bef67fadf6c80d036e","analyzedAt":"2026-09-06T11:37:12.241Z","contentChangedAt":"2026-09-06T11:37:12.241Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}