skip to content

For a POST that creates a resource, how do you correctly return 201 with a Location header using ResponseEntity, and how should you build that URI?

level: seniorimportance: should knowfreq 55%

answer

  1. created(uri) = 201 + Location
  2. ServletUriComponentsBuilder.fromCurrentRequest()
  3. buildAndExpand(id).toUri()
  4. ForwardedHeaderFilter behind proxy
  5. never hardcode the URI string

basics

~10 s

Use ResponseEntity.created(uri).body(dto) — created() sets 201 and the Location header. Build the URI from the current request, e.g. ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri(), so it reflects the real base URL.

solid answer

~40 s

ResponseEntity.created(URI) returns a 201 Created with the Location header set to that URI, which by REST convention points to the newly created resource. The important part is building an absolute, correct URI rather than hardcoding it. Use ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(newId).toUri() to derive it from the incoming request (host, scheme, context path), or inject a UriComponentsBuilder into the handler and expand a known template. Then return ResponseEntity.created(location).body(createdDto). This gives clients a follow-up URL and satisfies HTTP semantics for creation. Behind a proxy/load balancer you must configure ForwardedHeaderFilter (or trust X-Forwarded-* headers) so the scheme/host in Location are correct. Hardcoding "/orders/" + id produces a relative or wrong-host Location and is a common review flag.

code

java · 10 lines
java
@PostMapping("/orders")
public ResponseEntity<OrderDto> create(@RequestBody @Valid CreateOrder cmd) {
    OrderDto saved = orderService.create(cmd);
    URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()      // .../orders
            .path("/{id}")
            .buildAndExpand(saved.id())
            .toUri();                  // .../orders/{id} absolute
    return ResponseEntity.created(location).body(saved); // 201 + Location + body
}

go deeper

for a junior

Know created(uri) sets 201 and Location.

for a middle

Build the URI from the request rather than hardcoding; attach the created DTO as body.

for a senior

Use ServletUriComponentsBuilder/UriComponentsBuilder and handle proxy X-Forwarded headers.

for a principal

Standardize creation responses (201 + Location + representation), encoding, and forwarded-header config as a cross-cutting convention.

## The HTTP contract for creation When a `POST` creates a resource, the correct response is **`201 Created`** with a **`Location`** header holding the URI of the new resource (RFC 9110). Optionally the body carries a representation of the created resource. ## Doing it with ResponseEntity `ResponseEntity.created(URI location)` bundles both: it sets status **201** and the **Location** header to the given URI, and returns a `BodyBuilder` so you can attach the body: ```java return ResponseEntity.created(location).body(createdDto); ``` ## Building the URI correctly (the crux) Don't hand-concatenate strings. Two idiomatic approaches: 1. **`ServletUriComponentsBuilder.fromCurrentRequest()`** — derives scheme/host/port/context-path from the in-flight request: ```java URI location = ServletUriComponentsBuilder .fromCurrentRequest() // e.g. POST https://api.example.com/orders .path("/{id}") .buildAndExpand(saved.id()) .toUri(); // https://api.example.com/orders/42 ``` (`fromCurrentContextPath()` / `fromCurrentServletMapping()` are variants when the POST path differs from the resource path.) 2. **Inject `UriComponentsBuilder`** as a handler argument (Spring provides one bound to the current request) and expand a template: ```java URI location = ucb.path("/orders/{id}").buildAndExpand(saved.id()).toUri(); ``` Both produce an **absolute** URI reflecting the real base URL. ## Proxy / load-balancer correctness Behind a reverse proxy or LB, the app often sees `http://internal-host` while clients use `https://api.example.com`. To make `Location` reflect the external URL, register **`ForwardedHeaderFilter`** (Spring Boot: it honors `X-Forwarded-*` when `server.forward-headers-strategy=framework` or `native`). Without it, `fromCurrentRequest()` may emit an internal/incorrect host or scheme. ## Common mistakes - **Hardcoding** `URI.create("/orders/" + id)` — yields a relative or wrong-host Location. - Building from user-controlled input without validation — the id should come from the persisted entity, not raw request data. - Forgetting the body when clients expect the created representation (optional but common). - Returning **200** instead of **201**, losing the creation semantics and the Location convention. - Not URL-encoding path variables — `buildAndExpand` handles encoding when you call `.encode()` or rely on template expansion; be deliberate about `.encode()` for values with special characters. ## When you don't have the id yet If the resource id is generated by the DB, build the URI **after** save so the real id is available (as shown). For async creation you might return **202 Accepted** with a status/polling Location instead of 201.

  • The app runs behind an HTTPS load balancer but Location comes back as http://internal-host. Why, and how do you fix it?
    fromCurrentRequest() uses what the app server sees (internal http host). Enable ForwardedHeaderFilter / set server.forward-headers-strategy so Spring honors X-Forwarded-Proto/Host and emits the external https URL.
  • Why prefer ServletUriComponentsBuilder over string concatenation for Location?
    It produces an absolute URI using the real scheme/host/context path, handles path-variable encoding, and stays correct across environments — concatenation yields relative or wrong-host URIs.

saying these in an interview costs you the question

  • Hardcoding the Location URI by string concatenation
  • Returning 200 instead of 201 for a creation
  • Ignoring reverse-proxy headers so Location has the wrong scheme/host

context