skip to content

How do you build an absolute Location URL from the current request, e.g. for a 201 Created response?

level: middleimportance: must knowfreq 38%

answer

  1. ServletUriComponentsBuilder = request-aware subclass
  2. fromCurrentRequest().path('/{id}').buildAndExpand(id).toUri()
  3. fromCurrentContextPath -> link to other endpoint
  4. thread-local -> fails in @Async/@Scheduled
  5. honors X-Forwarded-* only with ForwardedHeaderFilter

basics

~10 s

Use ServletUriComponentsBuilder.fromCurrentRequest() (or fromCurrentContextPath()), append the new resource path, expand the id, and call toUri(). It reads the current request's scheme, host, and port so you don't hardcode them.

solid answer

~40 s

For a POST that creates a resource you return 201 Created with a Location header pointing at the new resource. Rather than hardcode the host, use ServletUriComponentsBuilder, a request-aware subclass of UriComponentsBuilder that pulls scheme/host/port/context-path from the current request via RequestContextHolder's thread-local. The idiom is ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri(), then ResponseEntity.created(uri). Key factories: fromCurrentRequest() (full current URL incl. query), fromCurrentRequestUri() (drops query), fromCurrentContextPath(), fromCurrentServletMapping(). Because it relies on a thread-local, it only works inside a request-processing thread; in async/scheduled code you must capture the builder or request earlier. It also honors forwarded headers (Forwarded / X-Forwarded-*) when a ForwardedHeaderFilter is configured, so behind a proxy the generated URL reflects the external host.

code

java · 20 lines
java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;

@RestController
@RequestMapping("/users")
class UserController {

    @PostMapping
    ResponseEntity<User> create(@RequestBody User body) {
        User saved = service.save(body);
        URI location = ServletUriComponentsBuilder
                .fromCurrentRequest()            // http://host/users
                .path("/{id}")                   // http://host/users/{id}
                .buildAndExpand(saved.getId())   // http://host/users/42
                .toUri();
        return ResponseEntity.created(location).body(saved); // 201 + Location
    }
}

go deeper

for a junior

Know the 201-Created idiom: fromCurrentRequest().path('/{id}').buildAndExpand(id).toUri() plus ResponseEntity.created.

for a middle

Distinguish the fromCurrent* factories and know it derives scheme/host/port from the live request via a thread-local.

for a senior

Explain the thread-local limitation for async code and the forwarded-header behavior; prefer injecting a UriComponentsBuilder param for testability.

for a principal

Own the proxy/forwarded-header strategy (ForwardedHeaderFilter + trusted-proxy boundary) and its host-header-spoofing security implications for generated links.

## The problem After `POST /users` creates user 42, REST convention says respond `201 Created` with a `Location: .../users/42` header. You must produce an **absolute** URL, but you don't want to hardcode `https://myhost:port` because it varies by environment and by proxy. ## ServletUriComponentsBuilder `ServletUriComponentsBuilder` (package `org.springframework.web.servlet.support`) extends `UriComponentsBuilder` and is **request-aware**: its static factories read the current `HttpServletRequest` from `RequestContextHolder` (a thread-local populated by Spring's `DispatcherServlet` / `RequestContextFilter`). So scheme, host, port, and context path are taken from the live request. ### Factory methods (what each seeds the builder with) - `fromCurrentRequest()` — the **entire current request URL**, including path and query string. - `fromCurrentRequestUri()` — current request URL **without** the query string. - `fromCurrentContextPath()` — scheme/host/port + the app's context path only (nothing else). Best base for building a link to a *different* endpoint. - `fromCurrentServletMapping()` — up to and including the servlet mapping. - Non-'current' variants (`fromRequest(HttpServletRequest)`, `fromContextPath(request)`, ...) take the request explicitly — useful outside the DispatcherServlet flow or in tests. ## Canonical 201 idiom ```java URI location = ServletUriComponentsBuilder .fromCurrentRequest() // .../users .path("/{id}") // .../users/{id} .buildAndExpand(saved.getId()) // .../users/42 .toUri(); return ResponseEntity.created(location).body(saved); ``` `ResponseEntity.created(location)` sets status 201 and the `Location` header. ## Building a link to a *different* endpoint Use `fromCurrentContextPath()` so you keep host/port/context but supply a fresh path: ```java URI link = ServletUriComponentsBuilder.fromCurrentContextPath() .path("/api/orders/{id}").buildAndExpand(orderId).toUri(); ``` ## Thread-local gotcha Because it reads `RequestContextHolder`, these `fromCurrent*` methods **only work on the request-handling thread**. In `@Async` methods, `@Scheduled` jobs, reactive callbacks, or new threads, the thread-local is empty and you'll get an `IllegalStateException` ("No thread-bound request found"). Capture the `UriComponentsBuilder` (Spring can inject one as a controller-method argument) or the needed values *before* leaving the request thread. ## Proxy / forwarded headers Behind a load balancer or reverse proxy, the request the app sees is `http://internal:8080` while the client used `https://public.example.com`. `ServletUriComponentsBuilder` honors `Forwarded` and `X-Forwarded-Host/Proto/Port/Prefix` headers so the generated URL matches the external address — **but only if you register Spring's `ForwardedHeaderFilter`** (or handle the headers at the proxy). This is also a security consideration: untrusted forwarded headers can poison generated links, so the filter should sit behind a trusted proxy or you should strip/validate those headers. ## Alternative: inject the builder A controller method can declare a `UriComponentsBuilder` parameter; Spring supplies a `ServletUriComponentsBuilder` pre-seeded from the current request, which is easy to pass into a service and mock in tests: ```java @PostMapping("/users") ResponseEntity<User> create(@RequestBody User u, UriComponentsBuilder ucb) { User saved = service.save(u); URI loc = ucb.path("/users/{id}").buildAndExpand(saved.getId()).toUri(); return ResponseEntity.created(loc).body(saved); } ```

  • Why might fromCurrentRequest() throw in an @Async method or scheduled job?
    It reads the current request from RequestContextHolder's thread-local, which is only bound on the request-handling thread. Off that thread it's empty and you get 'No thread-bound request found'. Capture the builder/values on the request thread first.
  • Behind a reverse proxy the Location header shows the internal host instead of the public one. Why, and how do you fix it?
    The app sees the proxy's internal scheme/host. Register Spring's ForwardedHeaderFilter so ServletUriComponentsBuilder applies Forwarded / X-Forwarded-* headers and reconstructs the external URL. Ensure the proxy is trusted so those headers can't be spoofed.

saying these in an interview costs you the question

  • Hardcoding host/port into the Location header instead of deriving it from the request.
  • Using fromCurrentRequest() inside async/scheduled code and expecting the thread-local to be populated.
  • Assuming forwarded headers are honored automatically without a ForwardedHeaderFilter.
  • Confusing fromCurrentRequest() (keeps query) with fromCurrentRequestUri() (drops query).

context