Spring ResponseEntity 详解:完整控制 HTTP 响应

前言

在 Spring MVC 或 Spring Boot 开发 REST API 时,我们最常见的写法可能是直接从 Controller 返回一个 Java 对象:

1
2
3
4
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}

Spring 会通过 HttpMessageConverter 将对象序列化成 JSON,并默认返回 200 OK

这种写法足够简单,但真实项目中的 HTTP 响应通常不只有响应体,还包括:

  • HTTP 状态码;
  • Response Header;
  • Response Body;
  • 缓存控制;
  • Location;
  • Content-Type;
  • ETag 等协议级信息。

当接口需要对这些内容进行精细控制时,ResponseEntity<T> 就非常合适。

ResponseEntity 的核心思想可以概括成一句话:

它不是单纯的数据包装类,而是 Spring 对一个完整 HTTP Response 的抽象。

本文结合实际开发场景,系统整理 ResponseEntity 的基本用法、RESTful API 设计方式、统一返回对象、异常处理、文件下载以及常见误区。


ResponseEntity 是什么

ResponseEntity<T> 位于:

1
org.springframework.http.ResponseEntity

它继承自 HttpEntity<T>

1
2
3
4
5
6
7
8
HttpEntity<T>
├── headers
└── body

ResponseEntity<T>
├── headers
├── body
└── statusCode

HttpEntity 负责描述 HTTP Header 和 Body,而 ResponseEntity 在此基础上增加了 HTTP Status Code。

因此,一个 ResponseEntity 可以完整表达:

1
2
3
4
HTTP Response
├── Status Code
├── Headers
└── Body

例如:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 7e91c4

{
"id": 1001,
"name": "Mario"
}

在 Java 中可以表示为:

1
ResponseEntity<User>

其中泛型 User 表示响应体的数据类型。


ResponseEntity 的基本语法

最直接的构造方式是:

1
2
ResponseEntity<T> response =
new ResponseEntity<>(body, headers, status);

例如:

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/hello")
public ResponseEntity<String> hello() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-App", "demo");

return new ResponseEntity<>(
"Hello Spring",
headers,
HttpStatus.OK
);
}

对应 HTTP 响应大致为:

1
2
3
4
5
HTTP/1.1 200 OK
X-App: demo
Content-Type: text/plain

Hello Spring

不过在实际开发中,更推荐使用 ResponseEntity 提供的 Builder API,因为可读性更好。


最常用的 Builder 写法

返回 200 OK

最常见的方式:

1
2
3
4
5
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}

等价于:

1
return new ResponseEntity<>(user, HttpStatus.OK);

如果只需要返回状态码:

1
return ResponseEntity.ok().build();

返回指定 HTTP 状态码

可以通过 status() 设置任意 HTTP 状态:

1
2
3
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("User not found");

也可以直接使用数字:

1
2
3
return ResponseEntity
.status(404)
.body("User not found");

通常还是建议优先使用 HttpStatus,代码语义更加清晰。


常见快捷方法

ResponseEntity 提供了大量常见 HTTP 状态的快捷 Builder。

200 OK

1
return ResponseEntity.ok(data);

201 Created

1
return ResponseEntity.created(location).body(data);

202 Accepted

1
return ResponseEntity.accepted().build();

204 No Content

1
return ResponseEntity.noContent().build();

400 Bad Request

1
return ResponseEntity.badRequest().body(error);

404 Not Found

1
return ResponseEntity.notFound().build();

这些方法比:

1
new ResponseEntity<>(..., HttpStatus.xxx)

通常更容易表达接口意图。


用 ResponseEntity 设计 CRUD API

ResponseEntity 最有价值的地方之一,就是让 Controller 能够更加准确地表达 HTTP 语义。

假设存在一个用户接口:

1
2
3
4
POST   /api/users
GET /api/users/{id}
PUT /api/users/{id}
DELETE /api/users/{id}

可以按照下面的方式设计。


查询资源:200 或 404

1
2
3
4
5
6
@GetMapping("/api/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

存在资源:

1
HTTP/1.1 200 OK

不存在资源:

1
HTTP/1.1 404 Not Found

如果 Service 返回的是 Optional<User>,还可以进一步简化:

1
2
3
4
@GetMapping("/api/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.of(userService.findById(id));
}

这种写法非常适合:

1
2
3
4
5
Optional.empty()
-> 404 Not Found

Optional<User>
-> 200 OK + User

创建资源:201 Created

对于创建资源的 POST 请求,更标准的返回状态通常是:

1
201 Created

并通过 Location Header 告诉客户端新资源的位置。

例如:

1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/api/users")
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {

User user = userService.create(request);

URI location = URI.create("/api/users/" + user.getId());

return ResponseEntity
.created(location)
.body(user);
}

返回:

1
2
3
HTTP/1.1 201 Created
Location: /api/users/1001
Content-Type: application/json

Body:

1
2
3
4
{
"id": 1001,
"name": "Mario"
}

ResponseEntity.created(location) 会同时表达两件事:

1
2
Status = 201 Created
Location = /api/users/1001

比单独写:

1
ResponseEntity.ok(user)

更符合资源创建的 HTTP 语义。


删除资源:204 No Content

删除成功后如果不需要返回数据,可以使用:

1
2
3
4
5
6
7
8
9
10
11
@DeleteMapping("/api/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {

boolean deleted = userService.delete(id);

if (!deleted) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.noContent().build();
}

删除成功:

1
HTTP/1.1 204 No Content

资源不存在:

1
HTTP/1.1 404 Not Found

需要注意:

204 No Content 的语义就是没有响应体,因此不要一边返回 204,一边又向 Body 中塞数据。


更新资源

更新成功后可以根据接口设计返回:

1
200 OK

或者:

1
204 No Content

例如返回更新后的对象:

1
2
3
4
5
6
7
8
9
@PutMapping("/api/users/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody UpdateUserRequest request) {

User user = userService.update(id, request);

return ResponseEntity.ok(user);
}

如果客户端不需要更新后的资源:

1
return ResponseEntity.noContent().build();

自定义 Response Header

除了状态码之外,ResponseEntity 的另一大用途是操作响应头。

例如:

1
2
3
4
5
6
7
8
@GetMapping("/api/header")
public ResponseEntity<String> customHeader() {
return ResponseEntity
.ok()
.header("X-Service-Version", "v1")
.header("X-Trace-Id", "7e91c4")
.body("success");
}

返回:

1
2
3
4
5
HTTP/1.1 200 OK
X-Service-Version: v1
X-Trace-Id: 7e91c4

success

也可以使用 HttpHeaders

1
2
3
4
5
HttpHeaders headers = new HttpHeaders();
headers.set("X-Trace-Id", traceId);
headers.setContentType(MediaType.APPLICATION_JSON);

return new ResponseEntity<>(data, headers, HttpStatus.OK);

设置 Content-Type

例如明确返回 JSON:

1
2
3
4
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(data);

如果返回文件:

1
2
3
4
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_PDF)
.body(resource);

一般情况下 Spring 会根据返回对象和 HttpMessageConverter 自动确定 Content-Type,只有接口存在特殊需求时才需要显式设置。


ResponseEntity 与泛型

ResponseEntity 本身是一个泛型类:

1
ResponseEntity<T>

因此 Body 可以是任意类型。

字符串:

1
ResponseEntity<String>

对象:

1
ResponseEntity<User>

集合:

1
ResponseEntity<List<User>>

分页对象:

1
ResponseEntity<Page<User>>

统一响应对象:

1
ResponseEntity<ApiResponse<User>>

文件:

1
ResponseEntity<Resource>

没有 Body:

1
ResponseEntity<Void>

泛型的意义是让接口返回值在编译期就具备明确类型,而不是大量使用:

1
ResponseEntity<Object>

不建议滥用 ResponseEntity

很多项目为了“方便”,会写成:

1
public ResponseEntity<Object> getUser(...)

然后不同情况下返回:

1
2
3
4
5
User
String
Map
List
Error

虽然代码可以运行,但是接口契约会变得模糊。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/users/{id}")
public ResponseEntity<Object> getUser(@PathVariable Long id) {

User user = userService.findById(id);

if (user == null) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("User not found");
}

return ResponseEntity.ok(user);
}

此时客户端无法仅通过接口签名判断 Body 到底是什么结构。

更推荐定义稳定的错误模型,或者使用统一响应对象。


ResponseEntity 与统一响应对象并不冲突

很多企业项目都会定义统一的 API 返回结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public record ApiResponse<T>(
int code,
String message,
T data
) {

public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(0, "success", data);
}

public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}

Controller:

1
2
3
4
5
6
7
8
9
@GetMapping("/api/users/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {

User user = userService.findById(id);

return ResponseEntity.ok(
ApiResponse.success(user)
);
}

这里其实存在两个不同层次:

1
2
3
4
5
6
7
8
9
10
11
HTTP 层
ResponseEntity
├── HTTP Status
├── HTTP Headers
└── Body

业务层
ApiResponse
├── code
├── message
└── data

因此二者并不是二选一。

可以理解为:

1
ResponseEntity<ApiResponse<User>>

其中:

1
2
3
4
5
ResponseEntity
-> 描述 HTTP 协议语义

ApiResponse
-> 描述业务响应结构

不要让所有接口永远返回 HTTP 200

很多传统项目会这样设计:

1
HTTP/1.1 200 OK

然后:

1
2
3
4
5
{
"code": 500,
"message": "system error",
"data": null
}

或者:

1
2
3
4
5
{
"code": 404,
"message": "user not found",
"data": null
}

这种设计并不是完全不能使用,但会削弱 HTTP 本身已经提供的状态语义。

例如监控系统、API Gateway、浏览器、负载均衡器、客户端 SDK,通常首先能直接看到 HTTP Status。

更清晰的设计是:

1
HTTP/1.1 404 Not Found

Body:

1
2
3
4
{
"code": "USER_NOT_FOUND",
"message": "User not found"
}

也就是说:

1
2
3
4
5
HTTP Status
-> 描述请求在 HTTP 层面的结果

Business Code
-> 描述业务系统内部的错误类型

两者各司其职。


使用 ResponseEntity 处理参数校验错误

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@PostMapping("/api/users")
public ResponseEntity<Map<String, String>> createUser(
@Valid @RequestBody CreateUserRequest request,
BindingResult bindingResult) {

if (bindingResult.hasErrors()) {

Map<String, String> errors = new HashMap<>();

bindingResult.getFieldErrors().forEach(error ->
errors.put(
error.getField(),
error.getDefaultMessage()
)
);

return ResponseEntity
.badRequest()
.body(errors);
}

userService.create(request);

return ResponseEntity
.status(HttpStatus.CREATED)
.build();
}

校验失败:

1
HTTP/1.1 400 Bad Request

Body:

1
2
3
4
{
"username": "用户名不能为空",
"email": "邮箱格式错误"
}

不过在大型项目中,不建议每一个 Controller 都手动写 BindingResult

更好的方式是将参数校验异常交给全局异常处理器。


ResponseEntity + @RestControllerAdvice

真实项目中,Controller 最好专注业务流程,而不是到处复制错误处理代码。

可以使用:

1
@RestControllerAdvice

配合:

1
@ExceptionHandler

统一构造 ResponseEntity

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiError> handleUserNotFound(
UserNotFoundException ex) {

ApiError error = new ApiError(
"USER_NOT_FOUND",
ex.getMessage()
);

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleException(Exception ex) {

ApiError error = new ApiError(
"INTERNAL_ERROR",
"Internal server error"
);

return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}

错误对象:

1
2
3
4
5
public record ApiError(
String code,
String message
) {
}

这样 Controller 就可以保持简单:

1
2
3
4
5
6
@GetMapping("/api/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(
userService.getById(id)
);
}

如果用户不存在:

1
throw new UserNotFoundException("User not found");

由异常处理层统一转换成:

1
2
HTTP/1.1 404 Not Found
Content-Type: application/json
1
2
3
4
{
"code": "USER_NOT_FOUND",
"message": "User not found"
}

Spring Boot 3 中可以结合 ProblemDetail

Spring Boot 3 基于 Spring Framework 6,可以使用 ProblemDetail 构造标准化 HTTP 错误响应。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ProblemDetail> handleUserNotFound(
UserNotFoundException ex) {

ProblemDetail problem =
ProblemDetail.forStatus(HttpStatus.NOT_FOUND);

problem.setTitle("User Not Found");
problem.setDetail(ex.getMessage());

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(problem);
}

对于面向外部开放的 REST API,ProblemDetail 可以减少自定义错误模型的重复设计。

如果是企业内部已有统一错误协议,也完全可以继续使用自己的 ApiError


分页接口

ResponseEntity 可以直接包装 Spring Data 的 Page<T>

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/api/users")
public ResponseEntity<Page<User>> listUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {

Pageable pageable = PageRequest.of(page, size);

Page<User> result = userService.findAll(pageable);

return ResponseEntity.ok(result);
}

不过对于公开 API,通常更推荐定义自己的分页 DTO,避免把框架内部的数据结构直接暴露给客户端。

例如:

1
2
3
4
5
6
7
public record PageResponse<T>(
List<T> records,
long total,
int page,
int size
) {
}

然后:

1
2
3
4
5
6
7
8
return ResponseEntity.ok(
new PageResponse<>(
result.getContent(),
result.getTotalElements(),
result.getNumber(),
result.getSize()
)
);

使用 ResponseEntity 下载文件

ResponseEntity 也非常适合文件下载,因为文件接口通常需要同时控制:

1
2
3
4
5
Content-Type
Content-Disposition
Content-Length
Body
Status

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@GetMapping("/api/files/{name}")
public ResponseEntity<Resource> download(
@PathVariable String name) {

Resource resource = fileService.load(name);

return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(
HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + name + "\""
)
.body(resource);
}

浏览器收到:

1
2
3
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="demo.pdf"

Response Body 则是文件内容。

对于文件、流、缓存资源等接口,ResponseEntity 往往比单纯返回对象更自然。


缓存控制

Response Header 并不仅仅是放自定义字段,还可以控制 HTTP Cache。

例如:

1
2
3
4
5
6
return ResponseEntity
.ok()
.cacheControl(
CacheControl.maxAge(Duration.ofMinutes(10))
)
.body(data);

还可以设置 ETag:

1
2
3
4
return ResponseEntity
.ok()
.eTag("\"user-1001-v3\"")
.body(user);

返回:

1
ETag: "user-1001-v3"

因此 ResponseEntity 不只是“改状态码”的工具,它可以参与完整的 HTTP 协议设计。


ResponseEntity 与 @ResponseBody 的区别

普通的:

1
2
3
4
5
@ResponseBody
@GetMapping("/hello")
public User hello() {
return user;
}

主要表达的是:

1
把返回值写入 HTTP Response Body

而:

1
2
3
4
@GetMapping("/hello")
public ResponseEntity<User> hello() {
return ResponseEntity.ok(user);
}

表达的是:

1
2
3
4
HTTP Response
├── Status
├── Header
└── Body

可以简单理解为:

1
2
3
4
5
@ResponseBody
-> 我主要关心 Body

ResponseEntity
-> 我需要控制完整 HTTP Response

@RestController 中已经隐含了 @ResponseBody,因此使用 ResponseEntity 时通常不需要额外添加 @ResponseBody


ResponseEntity 与 @ResponseStatus 的区别

Spring 还提供:

1
@ResponseStatus

例如:

1
2
3
4
5
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/api/users")
public User createUser(...) {
return userService.create(...);
}

这种方式适合状态码固定的场景。

但如果一个接口根据业务结果动态返回:

1
2
3
4
200
400
404
409

那么 ResponseEntity 更灵活。

例如:

1
2
3
4
5
if (user == null) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.ok(user);

因此:

1
2
3
4
5
@ResponseStatus
-> 静态状态码

ResponseEntity
-> 动态构造整个 HTTP Response

ResponseEntity 与 HttpServletResponse 的区别

也可以直接操作 Servlet API:

1
2
3
4
public void handle(HttpServletResponse response) {
response.setStatus(200);
response.setHeader("X-Test", "1");
}

但这种方式会让 Controller 与 Servlet API 强耦合。

而:

1
2
3
4
return ResponseEntity
.ok()
.header("X-Test", "1")
.body(data);

更加声明式,也更容易测试。

因此在普通 REST Controller 中,如果只是控制:

1
2
3
Status
Header
Body

通常优先考虑 ResponseEntity


是否所有 Controller 都应该返回 ResponseEntity

答案是:不需要。

例如:

1
2
3
4
@GetMapping("/api/config")
public Config getConfig() {
return configService.get();
}

如果接口永远都是:

1
200 OK + JSON

那么直接返回对象反而更加简单。

没有必要为了“统一”写成:

1
2
3
4
@GetMapping("/api/config")
public ResponseEntity<Config> getConfig() {
return ResponseEntity.ok(configService.get());
}

虽然没有错,但价值有限。

更适合使用 ResponseEntity 的场景包括:

  • 根据业务结果动态返回不同 HTTP Status;
  • 需要设置响应头;
  • RESTful CRUD 接口;
  • 文件下载;
  • Location;
  • Cache-Control;
  • ETag;
  • 重定向;
  • 全局异常处理;
  • 需要精确表达 HTTP 协议语义的接口。

因此可以遵循一个原则:

如果只需要返回 Body,就直接返回对象;如果需要控制 HTTP Response,就使用 ResponseEntity。


常见 HTTP 状态码

REST API 中常见状态码如下。

状态码 HttpStatus 常见场景
200 OK 请求成功
201 CREATED 创建资源成功
202 ACCEPTED 请求已接受,异步处理中
204 NO_CONTENT 请求成功但无需返回 Body
400 BAD_REQUEST 参数错误
401 UNAUTHORIZED 未认证
403 FORBIDDEN 已认证但无权限
404 NOT_FOUND 资源不存在
409 CONFLICT 资源状态冲突
422 UNPROCESSABLE_ENTITY 请求格式正确但业务语义无法处理
429 TOO_MANY_REQUESTS 请求过多
500 INTERNAL_SERVER_ERROR 服务端异常
503 SERVICE_UNAVAILABLE 服务暂时不可用

一个更完整的 Controller 示例

下面把前面的内容组合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.of(
userService.findById(id)
);
}

@PostMapping
public ResponseEntity<User> createUser(
@Valid @RequestBody CreateUserRequest request) {

User user = userService.create(request);

URI location = URI.create(
"/api/users/" + user.getId()
);

return ResponseEntity
.created(location)
.body(user);
}

@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {

User user = userService.update(id, request);

return ResponseEntity.ok(user);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(
@PathVariable Long id) {

userService.delete(id);

return ResponseEntity
.noContent()
.build();
}
}

整个接口的语义非常明确:

1
2
3
4
5
6
7
8
9
10
11
12
GET
成功 -> 200
不存在 -> 404

POST
成功 -> 201 + Location

PUT
成功 -> 200

DELETE
成功 -> 204

常见误区

误区一:ResponseEntity 只是统一返回对象

不是。

下面这种:

1
2
3
4
5
public class Result<T> {
private int code;
private String message;
private T data;
}

属于业务响应模型。

而:

1
ResponseEntity<T>

属于 HTTP Response 模型。

两者解决的问题不同。


误区二:用了 ResponseEntity 就必须再套 Result

不一定。

对于内部 API:

1
ResponseEntity<ApiResponse<User>>

当然可以。

但对于标准 REST API:

1
ResponseEntity<User>

同样完全合理。

错误响应可以由统一异常处理器转换成固定的错误格式。


误区三:接口错误也全部返回 200

例如:

1
HTTP 200
1
2
3
4
{
"code": 404,
"message": "not found"
}

会让 HTTP 层失去很多原本已经定义好的协议语义。

更推荐:

1
HTTP 404
1
2
3
4
{
"code": "USER_NOT_FOUND",
"message": "User not found"
}

误区四:204 还返回 Body

错误示例:

1
2
3
return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.body(data);

如果确实需要返回数据,应考虑使用:

1
200 OK

如果使用:

1
204 No Content

就应该:

1
return ResponseEntity.noContent().build();

误区五:所有接口都机械套 ResponseEntity

这种代码:

1
return ResponseEntity.ok(service.query());

本身没有问题。

但如果接口根本不需要控制状态码和 Header:

1
return service.query();

可能更加简洁。

技术抽象应该解决实际问题,而不是增加仪式感。


WebFlux 中的 ResponseEntity

在 Spring WebFlux 中,同样可以使用 ResponseEntity

例如:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/users/{id}")
public Mono<ResponseEntity<User>> getUser(
@PathVariable Long id) {

return userService.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(
ResponseEntity.notFound().build()
);
}

这里:

1
Mono<ResponseEntity<T>>

表示 HTTP 状态、Header 和 Body 都可以根据异步执行结果决定。

另一种形式:

1
ResponseEntity<Mono<T>>

则表示状态码和 Header 可以立即确定,而 Body 异步产生。

在普通 Spring MVC 项目中不用刻意关注这种区别,但在 WebFlux 项目中需要理解两者的时机差异。


最佳实践总结

实际项目中可以遵循下面几个原则。

1. 让 HTTP Status 表达 HTTP 结果

不要所有接口永远都是:

1
200 OK

应该合理使用:

1
2
3
4
5
6
7
8
9
201
204
400
401
403
404
409
429
500

2. 让业务 Code 表达业务错误

例如:

1
2
3
4
{
"code": "ACCOUNT_DISABLED",
"message": "Account has been disabled"
}

同时 HTTP Status:

1
HTTP/1.1 403 Forbidden

HTTP 协议和业务协议不要互相替代。


3. Controller 不要堆满异常判断

尽量把异常转换集中在:

1
@RestControllerAdvice

中统一处理。


4. 创建资源时使用 201 + Location

推荐:

1
2
3
return ResponseEntity
.created(location)
.body(resource);

5. 无 Body 时使用 ResponseEntity

例如:

1
ResponseEntity<Void>

比:

1
ResponseEntity<Object>

更能表达接口意图。


6. 尽量使用 Builder API

优先:

1
2
3
4
5
6
7
8
9
10
11
12
13
ResponseEntity.ok(data);

ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);

ResponseEntity
.created(location)
.body(data);

ResponseEntity
.noContent()
.build();

而不是在业务代码中到处堆:

1
new ResponseEntity<>(...)

Builder API 通常更加直观。


总结

ResponseEntity 是 Spring Web 开发中非常基础但又非常容易被低估的一个类。

它的核心价值并不是:

1
给返回对象再套一层包装

而是:

1
用 Java 对象完整表达一个 HTTP Response

也就是:

1
2
3
4
ResponseEntity<T>
├── HTTP Status
├── HTTP Headers
└── Response Body

在普通接口中,如果只需要:

1
200 OK + JSON

直接返回 Java 对象已经足够。

但当接口涉及:

1
2
3
4
5
6
7
8
RESTful 状态码
错误响应
Location
自定义 Header
文件下载
缓存
ETag
全局异常处理

ResponseEntity 就能够明显提高接口语义的清晰度。

真正值得掌握的不是:

1
ResponseEntity.ok(...)

这个方法本身,而是理解:

HTTP Status、Header 和 Body 本来就是一个完整协议响应的三个组成部分。

一旦从这个角度理解 ResponseEntity,很多 Spring REST API 的设计方式就会变得非常自然。


参考资料


Spring ResponseEntity 详解:完整控制 HTTP 响应
https://allendericdalexander.github.io/2026/08/13/java/spring/spring-responseentity-guide/
作者
AtLuoFu
发布于
2026年8月13日
许可协议