@ResponseStatus

Sets the HTTP status for a handler method's successful response, or for an exception class whenever it is thrown.

Signature
@ResponseStatus(HttpStatus.CREATED)
  • Added in Spring Boot Spring Framework 3.0

Parameters

ParameterTypeDescription
$valueoptionalHttpStatus= HttpStatus.INTERNAL_SERVER_ERRORAlias for code.
$codeoptionalHttpStatus= HttpStatus.INTERNAL_SERVER_ERRORThe status to send.
$reasonoptionalString= ""If set, Spring calls HttpServletResponse.sendError with this text; the method's return value is not written and the body comes from the error page. Avoid on REST endpoints.

Return value

n/aOn a handler method: the status used when the method returns normally. On an exception class: the status used when that exception (or a subclass) escapes a handler and no @ExceptionHandler deals with it.

@ResponseStatus declares a status code instead of building a ResponseEntity. It has two jobs, depending on where you put it.

On a handler method

It sets the status for a normal return. The common uses are CREATED on create endpoints, NO_CONTENT on void delete methods and ACCEPTED on endpoints that queue work. The status is fixed: if the method needs to choose between 200 and 201, or add a Location header, return ResponseEntity instead.

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
Tag create(@RequestBody NewTag body) { ... }

@ResponseStatus(HttpStatus.NOT_FOUND)
class TagNotFoundException extends RuntimeException { ... }

On an exception class

When an annotated exception escapes a controller and no @ExceptionHandler claims it, Spring sends that status. The body comes from Spring Boot's error handling, so its shape depends on your configuration, and the exception message is only included if you allow it. That makes this a quick way to get correct status codes, but a weak way to give clients useful detail. For full control, handle the exception in a @RestControllerAdvice, or extend ErrorResponseException so the exception carries its own problem detail for a ResponseEntityExceptionHandler to render.

The annotation also ties a domain exception to HTTP. That is acceptable in a small app; in a layered one, keep domain exceptions free of web annotations and map them in one advice class.

The reason trap

Setting reason makes Spring call response.sendError(status, reason). The servlet container then renders an error response, and on a handler method your return value is thrown away. On a 201 that means the created object never reaches the client. Leave reason empty on REST code.

Alternatives

  • ResponseStatusException: throw it with a status and message for one-off cases, no new class needed.
  • ErrorResponseException subclasses: carry a full RFC 9457 problem detail with type, title and extra properties.
  • ResponseEntity: when headers or the status depend on runtime values.

Example

Example · java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

record Tag(long id, String label) {}

record NewTag(String label) {}

@ResponseStatus(HttpStatus.NOT_FOUND)
class TagNotFoundException extends RuntimeException {
    TagNotFoundException(long id) {
        super("Tag " + id + " not found");
    }
}

@RestController
@RequestMapping("/api/tags")
public class TagController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)          // success status for this method
    Tag create(@RequestBody NewTag body) {
        return new Tag(7, body.label());
    }

    @GetMapping("/{id}")
    Tag one(@PathVariable long id) {
        throw new TagNotFoundException(id);      // becomes 404
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    void delete(@PathVariable long id) {
    }
}

When to use it

  • Returning 201 from a create endpoint without building a ResponseEntity
  • Making a void delete method answer 204 No Content
  • Giving a simple not-found exception a 404 in a small service

Frequently asked questions

Which wins, @ResponseStatus on the method or a ResponseEntity status?

The ResponseEntity. A method annotated @ResponseStatus(HttpStatus.CREATED) that returns ResponseEntity.ok(...) answers 200, because the entity's status is applied last. Pick one style per method so readers are not misled.

Why does my @ResponseStatus exception return 500?

Usually a broader @ExceptionHandler (for Exception or RuntimeException) caught it first. Handlers take precedence over the annotation.

More examples

ResponseStatusException for one-off cases

Throwing ResponseStatusException sets the status without declaring an exception class. Spring turns it into a response with that status; whether the message is shown depends on your error configuration.

Example · java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

record Voucher(String code, int percentOff) {}

@RestController
public class VoucherController {

    // No custom exception class needed for a one-off status.
    @GetMapping("/api/vouchers/{code}")
    Voucher voucher(@PathVariable String code) {
        if (!code.startsWith("SAVE")) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unknown voucher " + code);
        }
        return new Voucher(code, 10);
    }
}

An exception that carries its own problem detail

ErrorResponseException implements ErrorResponse. When a ResponseEntityExceptionHandler is registered (your own advice, or Boot's when spring.mvc.problemdetails.enabled=true), its ProblemDetail is written as application/problem+json with the type, title and custom sku property, and no extra handler is needed.

Example · java
import java.net.URI;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponseException;

// The exception carries a full problem detail. ResponseEntityExceptionHandler
// (yours, or Boot's with spring.mvc.problemdetails.enabled=true) renders it as is.
public class InsufficientStockException extends ErrorResponseException {

    public InsufficientStockException(String sku, int requested, int available) {
        super(HttpStatus.CONFLICT, problem(sku, requested, available), null);
    }

    private static ProblemDetail problem(String sku, int requested, int available) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT,
                "Only " + available + " of " + sku + " left, " + requested + " requested.");
        problem.setType(URI.create("https://api.example.com/problems/insufficient-stock"));
        problem.setTitle("Insufficient stock");
        problem.setProperty("sku", sku);
        return problem;
    }
}

Why reason breaks a success response

With reason set, Spring calls sendError(201, "Receipt created") and never writes the Receipt. The client sees an error-style body under a success status. Drop the reason attribute.

Example · java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

record Receipt(String id) {}

@RestController
public class ReasonPitfallController {

    // Do not do this: with reason set, Spring calls sendError(),
    // the returned Receipt is discarded and the client gets an error page body.
    @PostMapping("/api/receipts")
    @ResponseStatus(code = HttpStatus.CREATED, reason = "Receipt created")
    Receipt create() {
        return new Receipt("r-1");
    }
}

Discussion

  • Be the first to comment on this lesson.