{"record":{"id":"6a92f92c64aafb1f","repo":"grpc/grpc-java","slug":"outlier-detection-max-ejection-time-is-not-a-valid","errorCode":null,"errorMessage":"outlier_detection max_ejection_time is not a valid Duration","messagePattern":"outlier_detection max_ejection_time is not a valid Duration","errorType":"validation","errorClass":"ResourceInvalidException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/XdsClusterResource.java","lineNumber":396,"sourceCode":"        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())) {\n        throw new ResourceInvalidException(\n            \"outlier_detection max_ejection_time has a negative value\");\n      }\n    }\n    if (outlierDetection.hasMaxEjectionPercent()\n        && outlierDetection.getMaxEjectionPercent().getValue() > 100) {\n      throw new ResourceInvalidException(\n          \"outlier_detection max_ejection_percent is > 100\");\n    }\n    if (outlierDetection.hasEnforcingSuccessRate()\n        && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {\n      throw new ResourceInvalidException(\n          \"outlier_detection enforcing_success_rate is > 100\");\n    }\n    if (outlierDetection.hasFailurePercentageThreshold()","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/XdsClusterResource.java#L378-L414","documentation":"validateOutlierDetection throws this ResourceInvalidException when outlier_detection.max_ejection_time is present but fails Durations.isValid, meaning it is not a well-formed protobuf Duration (nanos/seconds out of the allowed range). The xDS client cannot parse the value so it rejects the entire Cluster resource rather than guessing. This surfaces during parseNonAggregateCluster when processing a CDS response.","triggerScenarios":"A Cluster resource contains outlier_detection.max_ejection_time as an invalid Duration — for example nanos = 1e9 or more without carrying into seconds, or seconds beyond the Duration range — passing through validateOutlierDetection.","commonSituations":"Buggy control-plane serialization producing out-of-range nanos; hand-written JSON fixtures; tests using fake durations like {nanos: -999999999999}; tools converting milliseconds into nanos without normalization.","solutions":["Provide a valid Duration, e.g. max_ejection_time: {seconds: 300}.","Remove the field to accept the default max ejection time.","Normalize nanos into seconds at the config generator.","Validate the resource with com.google.protobuf.util.Durations.isValid before emitting."],"exampleFix":"# before\noutlier_detection:\n  max_ejection_time: { nanos: 300000000000 }\n# after\noutlier_detection:\n  max_ejection_time: { seconds: 300 }","handlingStrategy":"validation","validationCode":"boolean validMaxEjectionTime(com.google.protobuf.Duration d) {\n  return com.google.protobuf.util.Durations.isValid(d)\n      && d.getNanos() >= 0 && d.getNanos() < 1000000000;\n}","typeGuard":"boolean isNormalizedDuration(com.google.protobuf.Duration d) {\n  return d != null && com.google.protobuf.util.Durations.isValid(d);\n}","tryCatchPattern":"try {\n  parseCluster(resource);\n} catch (io.grpc.xds.ResourceInvalidException e) {\n  if (e.getMessage().contains(\"max_ejection_time\")) {\n    log.error(\"max_ejection_time malformed in CDS resource\");\n  }\n  return null;\n}","preventionTips":["Convert milliseconds to Durations via Durations.fromMillis, not manual nanos math.","Assert Durations.isValid on every duration field before emitting proto JSON.","Watch for ms-to-ns overflow when scaling time values.","Test with realistic ejection times (30s base, 300s max)."],"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"}