symfony/http-kernel · error · HttpException
Missing header " ".
Error message
Missing header "%s".
What it means
Symfony's RequestHeaderValueResolver throws this HttpException when a controller argument mapped with #[MapRequestHeader] to a complex type (e.g. DateTime, Uid) is absent from the request headers, and the argument is neither nullable nor has a default value. The resolver aborts with the attribute's validationFailedStatusCode (default 422) because it cannot produce the argument value.
Solutions
- Send the required header with the exact name shown in the error message.
- Make the controller argument nullable (e.g. ?\DateTimeImmutable) so a missing header skips resolution.
- Give the argument a default value so absence is tolerated.
- Pass the header name explicitly via #[MapRequestHeader('X-Request-Id')] if the kebab-case derivation does not match what clients send.
Example fix
// before
public function show(#[MapRequestHeader] \DateTimeImmutable $lastModified) {}
// after
public function show(#[MapRequestHeader] ?\DateTimeImmutable $lastModified = null) {} Defensive patterns
Strategy: validation
Validate before calling
// Server side: guard before relying on the argument
public function show(Request $request, #[MapRequestHeader] ?\DateTimeImmutable $lastModified = null) {
if (null === $lastModified && $request->headers->has('last-modified') === false) {
// handle absent header explicitly
}
}
// Client side, before sending:
if (!isset($headers['X-Request-Id'])) {
throw new \LogicException('Header X-Request-Id is required');
} Type guard
function hasHeader(Request $request, string $name): bool {
return $request->headers->has($name);
} Try / catch
try {
$response = $kernel->handle($request);
} catch (HttpException $e) {
if (str_contains($e->getMessage(), 'Missing header ')) {
return new JsonResponse(['error' => $e->getMessage()], 422);
}
throw $e;
} Prevention
- Always send required custom headers in clients and functional tests (curl -H, Request::headers->set).
- Prefer explicit header names in #[MapRequestHeader('Name')] over parameter-name derivation.
- Make optional headers nullable or defaulted in the controller signature.
- Document required headers in the API spec and validate at the client boundary.
- Check proxies/CORS config for custom-header stripping (Access-Control-Allow-Headers).
When it happens
Trigger: A controller declares an argument like #[MapRequestHeader] \DateTimeImmutable $lastModified or #[MapRequestHeader] Uid $requestId, and the incoming request does not contain the corresponding header (name derived from the parameter in kebab-case, e.g. 'last-modified'), while the parameter is non-nullable with no default.
Common situations: Clients omitting a mandatory header (API version, trace id); header name mismatch after renaming a controller parameter since the header name is derived from it; tests or curl calls missing -H flags; proxies or CORS preflight policies stripping custom headers.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- The validation groups expression or closure must return a…
- Nested expressions in validation groups are not supported…
- Nested closures in validation groups are not supported. Use…
- GroupSequence cannot be used inside an array of validation…
- Validation groups must be strings.
AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13).
Data as JSON: /api/errors/aef7a71ad637b7a6.
Report an issue: GitHub.
Appendix: source
Thrown at Controller/ArgumentResolver/RequestHeaderValueResolver.php:44
{
if (!$attribute = $argument->getAttributesOfType(MapRequestHeader::class)[0] ?? null) {
return [];
}
$type = $argument->getType();
$name = $attribute->name ?? strtolower(preg_replace('/[a-z]\K[A-Z]/', '-$0', $argument->getName()));
if (!\in_array($type, ['string', 'array', AcceptHeader::class])) {
if (null === $type || $argument->isVariadic() || !class_exists($type) && !interface_exists($type)) {
throw new \LogicException(\sprintf('Could not resolve the argument typed "%s". Valid types are "array", "string" or "%s".', $type, AcceptHeader::class));
}
if (!$request->headers->has($name)) {
if ($argument->isNullable() || $argument->hasDefaultValue()) {
return [];
}
throw HttpException::fromStatusCode($attribute->validationFailedStatusCode, \sprintf('Missing header "%s".', $name));
}
// Stage the raw header under the argument name so that a resolver able to build this type,
// such as DateTimeValueResolver or UidValueResolver, picks it up from the attributes.
$request->attributes->set($argument->getName(), $request->headers->get($name));
throw new NearMissValueResolverException(\sprintf('#[MapRequestHeader] cannot build controller argument "$%s" of type "%s" by itself; no resolver converted the staged value.', $argument->getName(), $type));
}
$value = null;
if ($request->headers->has($name)) {
$value = match ($type) {
'string' => $request->headers->get($name),
'array' => match (strtolower($name)) {
'accept' => $request->getAcceptableContentTypes(),
'accept-charset' => $request->getCharsets(),
'accept-language' => $request->getLanguages(),View on GitHub (pinned to aa3a39d728)