Skip to content
Spring Boot sb web 3 min read

ResponseEntity & Status Codes

Returning a plain object from a handler always yields 200 OK. When you need to set a different status, add headers, or return an empty body, wrap the result in a ResponseEntity. This page covers its fluent builder, status-code selection, headers (including Location), and the lighter-weight @ResponseStatus.

Why ResponseEntity

ResponseEntity<T> represents the entire HTTP response — status line, headers, and body. It gives full control while still serializing the body through Jackson.

@GetMapping("/{id}")
public ResponseEntity<Product> one(@PathVariable Long id) {
    return service.findById(id)
            .map(ResponseEntity::ok)                  // 200 with body
            .orElse(ResponseEntity.notFound().build()); // 404 no body
}

The builder API

ResponseEntity exposes static factory methods and a fluent builder.

// 200 OK with a body
return ResponseEntity.ok(product);

// 201 Created with a body
return ResponseEntity.status(HttpStatus.CREATED).body(product);

// 204 No Content (empty body)
return ResponseEntity.noContent().build();

// 404 Not Found
return ResponseEntity.notFound().build();

// Custom status and headers
return ResponseEntity.status(HttpStatus.ACCEPTED)
        .header("X-Request-Id", requestId)
        .body(result);

build() finishes a response with no body; body(...) finishes one with a payload.

Status code reference

StatusConstantWhen to use
200OKSuccessful GET/PUT/PATCH with a body
201CREATEDResource created (return it + Location)
202ACCEPTEDAsync work accepted, not yet done
204NO_CONTENTSuccess with no body (DELETE)
400BAD_REQUESTMalformed input / validation failure
401UNAUTHORIZEDMissing/invalid authentication
403FORBIDDENAuthenticated but not allowed
404NOT_FOUNDResource does not exist
409CONFLICTState conflict (duplicate, version)
422UNPROCESSABLE_ENTITYSemantically invalid payload
500INTERNAL_SERVER_ERRORUnhandled server error

Location header on create

The REST convention for POST is to return 201 Created plus a Location header pointing at the new resource. Build the URI from the current request to avoid hard-coding the host.

@PostMapping
public ResponseEntity<Product> create(@Valid @RequestBody ProductRequest body) {
    Product saved = service.create(body);
    URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(saved.getId())
            .toUri();
    return ResponseEntity.created(location).body(saved);
}

Request:

curl -i -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Keyboard","price":59.99}'

Output:

HTTP/1.1 201 Created
Location: http://localhost:8080/api/products/101
Content-Type: application/json

{ "id": 101, "name": "Keyboard", "price": 59.99 }

Setting headers

Add headers fluently, or build an HttpHeaders object for several at once.

return ResponseEntity.ok()
        .header("Cache-Control", "max-age=60")
        .eTag("\"v3\"")
        .body(product);

@ResponseStatus

When a handler (or exception) always maps to a fixed status and you do not need the builder, @ResponseStatus is more concise. Spring applies it to the response automatically.

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Product create(@Valid @RequestBody ProductRequest body) {
    return service.create(body);
}

It shines on custom exceptions — throwing one yields the declared status without any ResponseEntity plumbing:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ProductNotFoundException extends RuntimeException {
    public ProductNotFoundException(Long id) {
        super("Product " + id + " not found");
    }
}
@GetMapping("/{id}")
public Product one(@PathVariable Long id) {
    return service.findById(id)
            .orElseThrow(() -> new ProductNotFoundException(id));
}

Output:

HTTP/1.1 404 Not Found

Note: Use ResponseEntity when status/headers vary per request (e.g. 200 vs 404). Use @ResponseStatus when the status is constant for the handler or exception type. For structured error bodies, prefer Problem Detail.

ResponseEntity vs @ResponseStatus

AspectResponseEntity@ResponseStatus
StatusDynamic, per callFixed, declared
HeadersFull controlNone
BodyAny, optionalMethod return value
Best forBranching responsesConstant status, exceptions

Pitfalls

  • Returning null from a handler gives 200 OK with an empty body, rarely what you want — use notFound() explicitly.
  • Do not hard-code host/port in Location; build it from the request with ServletUriComponentsBuilder.
  • @ResponseStatus on a handler that also returns a ResponseEntity is ignored — the entity wins.
Last updated June 13, 2026
Was this helpful?