如何在Spring Boot中转递下游REST服务的HttpStatus异常信息
你遇到的核心问题是:当你的Spring Boot枢纽服务调用其他REST应用抛出HttpClientErrorException时,你希望直接将原异常的状态码和自定义消息返回给用户,但当前通过@RestControllerAdvice重新抛出异常的方式,导致testRestTemplate在解析响应时因类型不匹配报错;而你又不想用返回空响应加警告头的方案,因为这会改变用户对服务的使用预期。
为什么原方案会失败?
你原来的@RestControllerAdvice是直接重新抛出HttpClientErrorException,此时Spring会自动生成一个默认的JSON格式错误响应体(结构类似{"timestamp":"...","status":418,"error":"I'm a teapot","message":"...","path":"/1001"})。但你的测试用例中,testRestTemplate期望解析出List<MyGenericType>类型的响应体,这就导致了JSON解析异常——因为实际返回的错误体结构和预期的列表类型完全不匹配,所以抛出了HttpMessageNotReadableException。
最优解决方案:构建匹配预期的错误响应
我们不需要重新抛出异常,而是直接构建一个包含正确状态码和自定义消息的响应实体,同时返回符合客户端预期的响应体结构(比如简单的JSON错误对象或纯文本),这样既保留了原异常的状态信息,又不会破坏客户端的解析逻辑。
1. 调整@RestControllerAdvice
这里我们返回一个结构化的JSON错误响应,状态码沿用原异常的状态码,消息使用自定义内容:
@RestControllerAdvice public class HubControllerAdvice { @ExceptionHandler(HttpClientErrorException.class) public ResponseEntity<Map<String, String>> handleHttpClientErrorException(HttpClientErrorException ex) { Map<String, String> errorBody = new HashMap<>(); errorBody.put("errorMessage", "Http client says: " + ex.getMessage()); return ResponseEntity.status(ex.getStatusCode()) .contentType(MediaType.APPLICATION_JSON) .body(errorBody); } }
如果你的客户端更接受纯文本消息,也可以改成返回文本类型:
@ExceptionHandler(HttpClientErrorException.class) public ResponseEntity<String> handleHttpClientErrorException(HttpClientErrorException ex) { String errorMessage = "Http client says: " + ex.getMessage(); return ResponseEntity.status(ex.getStatusCode()) .contentType(MediaType.TEXT_PLAIN) .body(errorMessage); }
2. 调整测试用例
因为现在服务不会抛出异常,而是返回一个带有错误状态码和响应体的ResponseEntity,所以测试需要验证响应的状态码和响应体内容,而不是断言抛出异常:
@Test @SuppressWarnings("unchecked") @DisplayName("should return original exception status and custom message") void shouldReturnOriginalExceptionStatusAndCustomMessage() { // 模拟被调用服务抛出异常 when(restTemplateMock.exchange(anyString(), any(), any(), any(ParameterizedTypeReference.class))) .thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT, "holy crap!")); // 调用枢纽服务,指定解析为错误响应的类型 ResponseEntity<Map<String, String>> response = testRestTemplate.exchange( url("/1001"), HttpMethod.GET, null, new ParameterizedTypeReference<Map<String, String>>() {} ); // 验证响应结果 assertThat(response.getStatusCode()).isEqualTo(HttpStatus.I_AM_A_TEAPOT); assertThat(response.getBody()).isNotNull(); assertThat(response.getBody().get("errorMessage")).isEqualTo("Http client says: holy crap!"); }
如果是纯文本响应,测试用例改为:
ResponseEntity<String> response = testRestTemplate.exchange( url("/1001"), HttpMethod.GET, null, String.class ); assertThat(response.getBody()).isEqualTo("Http client says: holy crap!");
方案优势
这个方案既满足了你的核心需求——将原异常的状态码和自定义消息返回给用户,又不需要改变用户的使用预期:
- 用户仍然可以通过响应状态码判断错误类型
- 响应体结构清晰,客户端可以正常解析
- 完全符合REST服务的常规错误处理范式
内容的提问来源于stack exchange,提问作者jactor-rises

