@RequestHeader

Binds an HTTP request header, such as a tenant id or Accept-Language, to a method parameter.

Signature
@RequestHeader(name = "X-Tenant-Id", required = false) String tenant
  • Added in Spring Boot Spring Framework 3.0

Parameters

ParameterTypeDescription
$valueoptionalString= ""Alias for name.
$nameoptionalString= ""The header name. Matching is case-insensitive, as HTTP requires.
$requiredoptionalboolean= trueWhether the header must be present. A missing required header gives 400.
$defaultValueoptionalString= noneValue used when the header is missing or empty; implies required = false.

Return value

n/aBinds a request header value to the parameter with type conversion. On a Map, MultiValueMap or HttpHeaders parameter it binds all headers.

Errors

A missing required header gives 400 via MissingRequestHeaderException.

@RequestHeader reads a request header into a method parameter. Headers carry metadata about a request rather than its subject: a tenant or correlation id, the preferred language, an idempotency key, a conditional request tag such as If-Match. Header names are case-insensitive, so x-tenant-id and X-Tenant-Id bind the same.

Required by default

Like @RequestParam, a header is required unless you say otherwise. A missing required header gives 400 with MissingRequestHeaderException. Make it optional with required = false (you get null), with defaultValue, or with an Optional parameter.

@GetMapping("/api/greeting")
Greeting greet(@RequestHeader("X-Tenant-Id") String tenant,
               @RequestHeader(name = "X-Client-Version", defaultValue = "unknown") String clientVersion)

Values are converted like other parameters. A comma-separated header binds to a List<String>. To see every header, declare @RequestHeader HttpHeaders headers, which also gives typed accessors such as getAccept(). For the client's language, declare a plain Locale parameter instead of reading Accept-Language: the locale resolver understands quality values and falls back to a default.

When not to use it

  • Authorization. With Spring Security, the filter chain has already validated the token. Reading Authorization yourself and parsing it duplicates security logic badly. Inject the authenticated principal instead.
  • Cross-cutting headers on every endpoint. A correlation id that every request carries belongs in a filter that puts it into the logging context, not in a parameter on fifty methods.
  • Content negotiation. Do not branch on Accept by hand; declare produces on the mapping and let Spring choose.

Trust

Clients control every header. X-Tenant-Id sent by a browser proves nothing: check that the authenticated user belongs to that tenant. Headers that should only come from your own proxy, such as X-Forwarded-For, must be stripped or overwritten at the edge, otherwise any client can forge them.

Example

Example · java
import java.util.Locale;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

record Greeting(String tenant, String language, String message) {}

@RestController
public class GreetingController {

    @GetMapping("/api/greeting")
    // A Locale parameter is resolved from Accept-Language by the LocaleResolver,
    // which understands quality values such as "fr-CH, fr;q=0.9, en;q=0.8".
    Greeting greet(@RequestHeader("X-Tenant-Id") String tenant,                       // required
                   @RequestHeader(name = "X-Client-Version", defaultValue = "unknown") String clientVersion,
                   Locale locale) {
        String message = Map.of("fr", "Bonjour", "de", "Hallo").getOrDefault(locale.getLanguage(), "Hello");
        return new Greeting(tenant + "/" + clientVersion, locale.getLanguage(), message);
    }
}

When to use it

  • Selecting the tenant in a multi-tenant API from an X-Tenant-Id header
  • Localising a message with the client's Accept-Language
  • Accepting an Idempotency-Key on payment endpoints

Frequently asked questions

Are header names case-sensitive?

No. HTTP header names are case-insensitive and Spring matches them that way, so the annotation's capitalisation does not matter.

How do I set a response header?

Return a ResponseEntity and use its header(name, value) builder method, or add an HttpServletResponse parameter and call setHeader.

More examples

Reading every header with HttpHeaders

An HttpHeaders parameter receives all request headers with case-insensitive lookups and typed helpers such as getAccept().

Example · java
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DebugController {

    // HttpHeaders gives case-insensitive access to every header.
    @GetMapping("/api/debug/client")
    Map<String, String> client(@RequestHeader HttpHeaders headers) {
        String agent = headers.getFirst(HttpHeaders.USER_AGENT);
        return Map.of("userAgent", agent != null ? agent : "unknown",
                      "accept", String.valueOf(headers.getAccept()));
    }
}

A clear error for a missing header

MissingRequestHeaderException carries the header name. Returning it in a problem detail tells client developers exactly what to add.

Example · java
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class HeaderErrors {

    // Name the missing header so client developers can fix their call.
    @ExceptionHandler(MissingRequestHeaderException.class)
    ProblemDetail missingHeader(MissingRequestHeaderException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST,
                "Required header '" + ex.getHeaderName() + "' is missing.");
        problem.setTitle("Missing header");
        return problem;
    }
}

An optional correlation id

When the caller does not send X-Request-Id, the method generates one and returns it as a response header. The value is client input that ends up in logs, so check its length and characters (or replace it) before using it. In a larger app this belongs in a filter so every endpoint behaves the same.

Example · java
import java.util.Map;
import java.util.UUID;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TraceController {

    // Optional header: generate an id when the caller did not send one,
    // and echo it back so both sides can find the request in their logs.
    @GetMapping("/api/stock/{sku}")
    ResponseEntity<Map<String, Integer>> stock(@RequestHeader(name = "X-Request-Id", required = false) String requestId) {
        String id = requestId != null ? requestId : UUID.randomUUID().toString();
        return ResponseEntity.ok()
                .header("X-Request-Id", id)
                .body(Map.of("available", 12));
    }
}

Discussion

  • Be the first to comment on this lesson.