43
votes

What is the preferred way to specify an HTTP "Location" Response Header in Spring MVC 3?

As far as I can tell, Spring will only provide a "Location" in response to a redirect ("redirect:xyz" or RedirectView), however there are scenarios where a Location should be sent along with the entity body (ex, as a result of a "201 Created").

I'm afraid my only option is manually specifying it:

httpServletResponse.setHeader("Location", "/x/y/z");

Is this correct? Is there a better way to tackle this problem?

5
Whats wrong with that approach?Gandalf
That's the good way of doing it, and it conforms to the http specs.rds

5 Answers

98
votes

The key point is to use UriComponentsBuilder. There are several ways how you can get the instance of it

  1. preconfigured UriComponentsBuilder from MvcUriComponentsBuilder
  2. UriComponentsBuilder injected as parameter to method

Preconfigured UriComponentsBuilder from MvcUriComponentsBuilder

This way you can get UriComponentsBuilder that is configured to produce URI that points to some controller methods with predefined parameters.

Here is example from the javadoc for MvcUriComponentsBuilder:

For example, given this controller:

 @RequestMapping("/people/{id}/addresses")
 class AddressController {

   @RequestMapping("/{country}")
   public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) { ... }

   @RequestMapping(value="/", method=RequestMethod.POST)
   public void addAddress(Address address) { ... }
 }

A UriComponentsBuilder can be created:

 // Inline style with static import of "MvcUriComponentsBuilder.on"

 MvcUriComponentsBuilder.fromMethodCall(
    on(AddressController.class).getAddressesForCountry("US")).buildAndExpand(1);

Another options which sometimes may be preferable is to specify controller method by name:

UriComponents uriComponents = MvcUriComponentsBuilder.fromMethodName(
    AddressController.class, "getAddressesForCountry", "US").buildAndExpand(1);
URI nextUri = uriComponents.toUri();

UriComponentsBuilder injected as parameter to method

As of spring 3.1 Location can be crafted using UriComponentBuilder parameter and set it to the returned ResponseEntity. UriComponentBuilder is aware of the context and manipulates with relative paths:

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createCustomer(UriComponentsBuilder b) {

    UriComponents uriComponents = 
        b.path("/customers/{id}").buildAndExpand(id);

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(uriComponents.toUri());
    return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}

Since version 4.1 you can make it even shorter

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createCustomer(UriComponentsBuilder b) {

    UriComponents uriComponents = 
        b.path("/customers/{id}").buildAndExpand(id);

    return ResponseEntity.created(uriComponents.toUri()).build();
}

Thanks to Dieter Hubau to pointing this out.

37
votes

The following example is from spring tutorial:

@RequestMapping(method = RequestMethod.POST)
ResponseEntity<?> add(@PathVariable String userId, @RequestBody Bookmark input) {
    this.validateUser(userId);

    return this.accountRepository
            .findByUsername(userId)
            .map(account -> {
                Bookmark result = bookmarkRepository.save(new Bookmark(account,
                        input.uri, input.description));

                URI location = ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/{id}")
                    .buildAndExpand(result.getId()).toUri();

                return ResponseEntity.created(location).build();
            })
            .orElse(ResponseEntity.noContent().build());

}

Take note that the following will compute the context path (URI) for you avoiding code duplication and making your application more portable:

ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/{id}")
6
votes

It's an old question but here is what you can do if you want to let Spring really build the URI for you.

@RestController
@RequestMapping("/api/v1")
class JobsController {

  @PostMapping("/jobs")
  fun createJob(@RequestParam("use-gpu") useGPU: Boolean?): ResponseEntity<Unit> {

    val headers = HttpHeaders()

    val jobId = "TBD id"

    headers.location =
            MvcUriComponentsBuilder
                    .fromMethodName(JobsController::class.java, "getJob", jobId)
                    .buildAndExpand(jobId)
                    .toUri()

    return ResponseEntity(headers, HttpStatus.CREATED)
  }

  @GetMapping("/job/{jobId}")
  fun getJob(@PathVariable jobId: String) = ... // fetch job
}

In this example (which is written in Kotlin but similar for java), the base URI is /api/v1 (defined at the top of the class). Using MvcUriComponentsBuilder.fromMethodName call lets Spring figure out the proper full URI. (MvcUriComponentsBuilder was added in 4.0).

2
votes

According to: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30

Absolute URI should be used:

Location       = "Location" ":" absoluteURI  

And the URI should be escaped properly:

http://www.ietf.org/rfc/rfc2396.txt

0
votes

Your approach seems fine, but to keep it clean you could put the code inside a custom HandlerInterceptor that only triggers when there's an HTTP 201, for example.

See here for more information.