{"record":{"id":"2ed2be8b6aa0136f","repo":"dotnet/yarp","slug":"header-values-must-have-at-least-one-value","errorCode":null,"errorMessage":"Header values must have at least one value.","messagePattern":"Header values must have at least one value\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ReverseProxy/Routing/HeaderMatcher.cs","lineNumber":29,"sourceCode":"\n/// <summary>\n/// A request header matcher used during routing.\n/// </summary>\ninternal sealed class HeaderMatcher\n{\n    /// <summary>\n    /// Creates a new instance.\n    /// </summary>\n    public HeaderMatcher(string name, IReadOnlyList<string>? values, HeaderMatchMode mode, bool isCaseSensitive)\n    {\n        if (string.IsNullOrEmpty(name))\n        {\n            throw new ArgumentException(\"A header name is required.\", nameof(name));\n        }\n        if ((mode != HeaderMatchMode.Exists && mode != HeaderMatchMode.NotExists)\n            && (values is null || values.Count == 0))\n        {\n            throw new ArgumentException(\"Header values must have at least one value.\", nameof(values));\n        }\n        if ((mode == HeaderMatchMode.Exists || mode == HeaderMatchMode.NotExists) && values?.Count > 0)\n        {\n            throw new ArgumentException($\"Header values must not be specified when using '{mode}'.\", nameof(values));\n        }\n        if (values is not null && values.Any(string.IsNullOrEmpty))\n        {\n            throw new ArgumentNullException(nameof(values), \"Header values must be not be empty.\");\n        }\n\n        Name = name;\n        Values = values?.ToArray() ?? Array.Empty<string>();\n        Mode = mode;\n        Comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;\n        Separator = name.Equals(HeaderNames.Cookie, StringComparison.OrdinalIgnoreCase) ? ';' : ',';\n    }\n\n    /// <summary>","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Routing/HeaderMatcher.cs#L11-L47","documentation":"The `HeaderMatcher` constructor requires at least one value for matching modes that compare values (`ExactHeader`, `HeaderPrefix`, `Contains`, `NotContains`). Only `Exists` and `NotExists` modes are exempt because they don't need values. This validation ensures the matcher has the data it needs to perform the comparison.","triggerScenarios":"A route header match is configured with a mode like `ExactHeader` (the default) but with an empty or null `Values` array (e.g., `{ \"Header\": \"X-Key\", \"Values\": [] }` or `{ \"Header\": \"X-Key\" }` without specifying values and without setting `Mode` to `Exists`). The constructor check at line 26-30 detects `values is null || values.Count == 0` and throws.","commonSituations":"A developer wants to check header *existence* but doesn't set `Mode: \"Exists\"` — the default mode is `ExactHeader`, which requires values. A config template has a placeholder values array that was never populated. A dynamic config provider omits values when they should have been populated.","solutions":["If you want to match on header existence, set `Mode` to `\"Exists\"` (or `\"NotExists\"`) and omit values — this is the most common fix.","If matching on specific values, populate the `Values` array with at least one value: `{ \"Header\": \"X-Key\", \"Values\": [\"expected-value\"] }`.","Verify the JSON structure — `Values` must be an array of strings, not a single string value.","Check for trailing commas or missing entries in the values array that could produce an empty collection."],"exampleFix":"// before — values missing with default ExactHeader mode\n\"Headers\": [\n  { \"Header\": \"X-Trace-Id\" } // throws — no values, default mode is ExactHeader\n]\n// after — use Exists mode if you only need header presence\n\"Headers\": [\n  { \"Header\": \"X-Trace-Id\", \"Mode\": \"Exists\" }\n]\n// or provide values if you need value matching\n\"Headers\": [\n  { \"Header\": \"X-Env\", \"Values\": [\"prod\", \"staging\"] }\n]","handlingStrategy":"validation","validationCode":"// Validate header match values/mode before adding to route\nstatic bool IsValidHeaderMatchValues(HeaderMatchConfig match)\n{\n    var needsValues = match.Mode is not (\"Exists\" or \"NotExists\");\n    return !needsValues || (match.Values is { Count: > 0 });\n}","typeGuard":"static bool IsValidHeaderMatcherConfig(string name, IReadOnlyList<string>? values, HeaderMatchMode mode)\n    => !string.IsNullOrEmpty(name)\n    && ((mode is HeaderMatchMode.Exists or HeaderMatchMode.NotExists) || (values is { Count: > 0 }));","tryCatchPattern":"// Not applicable — fix the configuration to provide values or use Exists mode.","preventionTips":["If checking header existence, explicitly set Mode to Exists.","Always provide at least one value for ExactHeader, HeaderPrefix, Contains, and NotContains modes.","Validate dynamic config generation to ensure header match values are populated."],"tags":["configuration","routing","header-matcher","validation"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}