{"record":{"id":"adbddf34f141e293","repo":"openzipkin/zipkin","slug":"maxduration-is-only-valid-with-minduration","errorCode":null,"errorMessage":"maxDuration is only valid with minDuration","messagePattern":"maxDuration is only valid with minDuration","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"zipkin/src/main/java/zipkin2/storage/QueryRequest.java","lineNumber":259,"sourceCode":"      // coerce service and span names to lowercase\n      if (serviceName != null) serviceName = serviceName.toLowerCase(Locale.ROOT);\n      if (remoteServiceName != null) remoteServiceName = remoteServiceName.toLowerCase(Locale.ROOT);\n      if (spanName != null) spanName = spanName.toLowerCase(Locale.ROOT);\n\n      if (\"\".equals(serviceName)) serviceName = null;\n      if (\"\".equals(remoteServiceName)) remoteServiceName = null;\n      if (\"\".equals(spanName) || \"all\".equals(spanName)) spanName = null;\n\n      if (endTs <= 0) throw new IllegalArgumentException(\"endTs <= 0\");\n      if (limit <= 0) throw new IllegalArgumentException(\"limit <= 0\");\n      if (lookback <= 0) throw new IllegalArgumentException(\"lookback <= 0\");\n      if (minDuration != null) {\n        if (minDuration <= 0) throw new IllegalArgumentException(\"minDuration <= 0\");\n        if (maxDuration != null && maxDuration < minDuration) {\n          throw new IllegalArgumentException(\"maxDuration < minDuration\");\n        }\n      } else if (maxDuration != null) {\n        throw new IllegalArgumentException(\"maxDuration is only valid with minDuration\");\n      }\n\n      return new QueryRequest(\n        serviceName,\n        remoteServiceName,\n        spanName,\n        annotationQuery,\n        minDuration,\n        maxDuration,\n        endTs,\n        lookback,\n        limit\n      );\n    }\n\n    Builder() {\n    }\n  }","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/openzipkin/zipkin/blob/878ce2a1fad54ca941d17fdcf2e1d924b148eb1f/zipkin/src/main/java/zipkin2/storage/QueryRequest.java#L241-L277","documentation":"Thrown by QueryRequest.Builder.build() when maxDuration is set while minDuration is null. Zipkin's query model treats maxDuration only as the upper bound of a duration filter whose lower bound is minDuration; an upper bound alone is not a supported query, so the builder rejects the combination.","triggerScenarios":"Calling .maxDuration(500000) without ever calling .minDuration(...); conditionally setting minDuration (e.g. only when a checkbox is ticked) while always setting maxDuration; clearing minDuration to null in shared builder code but leaving maxDuration set.","commonSituations":"A UI where users can set 'max duration' alone and the backend forwards it verbatim; refactoring that removed the minDuration call but kept maxDuration; copying a query template and deleting the minDuration line.","solutions":["Set a minDuration whenever you set maxDuration, e.g. .minDuration(1).maxDuration(500000)","If you only need an upper bound, keep minDuration very small (1 microsecond) rather than omitting it","Guard at the call site: skip both duration filters unless minDuration is present"],"exampleFix":"// before\nQueryRequest.newBuilder().endTs(endTs).limit(10)\n    .maxDuration(60000000L) // throws: only valid with minDuration\n    .build();\n\n// after\nQueryRequest.newBuilder().endTs(endTs).limit(10)\n    .minDuration(1L)\n    .maxDuration(60000000L)\n    .build();","handlingStrategy":"validation","validationCode":"// maxDuration requires minDuration; substitute a minimal lower bound when only max is set\nLong effectiveMin = (minDuration != null) ? minDuration\n                     : (maxDuration != null ? 1L : null);\nQueryRequest.Builder b = QueryRequest.newBuilder().endTs(endTs).limit(10);\nif (effectiveMin != null) b.minDuration(effectiveMin);\nif (maxDuration != null) b.maxDuration(maxDuration);","typeGuard":null,"tryCatchPattern":"try {\n    builder.maxDuration(maxDurationMicros).build();\n} catch (IllegalArgumentException e) {\n    // contract violation: set minDuration(1L) or drop maxDuration, then rebuild once\n    if (\"maxDuration is only valid with minDuration\".equals(e.getMessage())) {\n        builder.minDuration(1L).build();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Encapsulate the pairing rule in one query-builder wrapper so callers cannot set max without min","Document in your API that maxDuration alone is unsupported","Assert the invariant (max set implies min set) before calling build()"],"tags":["java","zipkin","validation","duration","api-contract"],"backgroundTag":null,"analyzedSha":"878ce2a1fad54ca941d17fdcf2e1d924b148eb1f","analyzedAt":"2026-08-14T15:17:09.895Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}