{"record":{"id":"5c74d4c633044247","repo":"grpc/grpc-java","slug":"outlier-detection-interval-has-a-negative-value","errorCode":null,"errorMessage":"outlier_detection interval has a negative value","messagePattern":"outlier_detection interval has a negative value","errorType":"validation","errorClass":"ResourceInvalidException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/XdsClusterResource.java","lineNumber":381,"sourceCode":"      String dnsHostName = String.format(\n          Locale.US, \"%s:%d\", socketAddress.getAddress(), socketAddress.getPortValue());\n      return StructOrError.fromStruct(CdsUpdate.forLogicalDns(\n          clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests,\n          upstreamTlsContext, isHttp11ProxyAvailable, backendMetricPropagation));\n    }\n    return StructOrError.fromError(\n        \"Cluster \" + clusterName + \": unsupported built-in discovery type: \" + type);\n  }\n\n  static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(\n      io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)\n      throws ResourceInvalidException {\n    if (outlierDetection.hasInterval()) {\n      if (!Durations.isValid(outlierDetection.getInterval())) {\n        throw new ResourceInvalidException(\"outlier_detection interval is not a valid Duration\");\n      }\n      if (hasNegativeValues(outlierDetection.getInterval())) {\n        throw new ResourceInvalidException(\"outlier_detection interval has a negative value\");\n      }\n    }\n    if (outlierDetection.hasBaseEjectionTime()) {\n      if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {\n        throw new ResourceInvalidException(\n            \"outlier_detection base_ejection_time is not a valid Duration\");\n      }\n      if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {\n        throw new ResourceInvalidException(\n            \"outlier_detection base_ejection_time has a negative value\");\n      }\n    }\n    if (outlierDetection.hasMaxEjectionTime()) {\n      if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {\n        throw new ResourceInvalidException(\n            \"outlier_detection max_ejection_time is not a valid Duration\");\n      }\n      if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/XdsClusterResource.java#L363-L399","documentation":"This ResourceInvalidException is thrown by XdsClusterResource.validateOutlierDetection when an xDS Cluster's outlier_detection.interval field is set to a Duration containing negative components (seconds or nanos). The library validates every xDS cluster resource received from the control plane and rejects configs it cannot faithfully implement. A negative interval would make the passive health-checking schedule meaningless, so the resource is rejected instead of being silently normalized.","triggerScenarios":"An xDS management server (or hand-written proto/JSON Cluster config) sends a Cluster whose outlier_detection.interval is a Duration with a negative seconds or nanoseconds value, e.g. interval: {seconds: -5} or {seconds: 0, nanos: -100}. The value must pass Durations.isValid() first, so this fires only for structurally valid but negative durations during parseNonAggregateCluster.","commonSituations":"Control-plane bugs emitting default/negative durations; hand-edited bootstrap or LDS/CDS YAML where a '-' was left in the value; templated config generators producing -1 as a sentinel for 'unset'; proto JSON where nanos is negative to represent a truncation artifact.","solutions":["Fix the control plane or config so outlier_detection.interval is a positive duration (e.g. {seconds: 10}).","If the field should be unset, remove the interval field entirely rather than sending a zero/negative value.","Verify with the envoy protos that Duration values use non-negative seconds and nanos (nanos in [0, 999999999]).","Upgrade the xDS management server if it is emitting negative defaults."],"exampleFix":"# before (CDS response)\noutlier_detection:\n  interval: { seconds: -5 }\n# after\noutlier_detection:\n  interval: { seconds: 10 }","handlingStrategy":"validation","validationCode":"import static com.google.protobuf.util.Durations.*;\nboolean isValidInterval(com.google.protobuf.Duration d) {\n  return isValid(d) && d.getSeconds() >= 0 && d.getNanos() >= 0;\n}\n// apply to cluster.getOutlierDetection().getInterval() when hasInterval()","typeGuard":"boolean nonNegativeDuration(com.google.protobuf.Duration d) {\n  return d != null && com.google.protobuf.util.Durations.isValid(d)\n      && d.getSeconds() >= 0 && d.getNanos() >= 0;\n}","tryCatchPattern":"try {\n  cluster = XdsClusterResource.parseCluster(rawCluster, ...);\n} catch (io.grpc.xds.ResourceInvalidException e) {\n  log.warn(\"rejecting invalid CDS resource: \" + e.getMessage());\n  return null; // skip resource, keep previous config\n}","preventionTips":["Validate Duration fields (seconds >= 0, nanos in [0, 999999999]) in the control plane before publishing.","Never use -1 as a sentinel for unset durations; omit the field instead.","Test generated Cluster resources against protobuf Duration validity rules.","Pin and test the management server version that serializes CDS."],"tags":["xds","grpc","config-validation","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}