You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中RestTemplate与异常处理器的异常信息获取问题

解决RestTemplate调用时无法获取对方服务异常详情的问题

我完全懂你的困扰:用RestTemplate调用其他服务时,对方抛出异常返回的ErrorDetails自定义JSON被默认的错误处理器吞掉了,只能拿到笼统的HttpServerErrorException,还陷入了postForLocation能拿location但异常信息拿不到,postForObject能处理响应但拿不到location的两难处境。下面给你两个针对性的解决方案:

方案一:自定义RestTemplate错误处理器,解析异常响应体

RestTemplate默认的DefaultResponseErrorHandler会把非成功状态码(非2xx/3xx)直接包装成Spring自带的HTTP异常,不会解析响应体里的自定义错误信息。我们可以自定义一个错误处理器,把响应体解析成ErrorDetails,再抛出携带这些信息的自定义异常。

步骤1:实现自定义ResponseErrorHandler

import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        // 先解析响应体中的ErrorDetails
        ErrorDetails errorDetails = objectMapper.readValue(response.getBody(), ErrorDetails.class);
        // 抛出自定义异常,携带错误详情和状态码
        throw new RemoteServiceException("远程服务调用失败", errorDetails, response.getStatusCode());
    }
}

// 自定义异常类,用来传递ErrorDetails信息
public class RemoteServiceException extends RuntimeException {
    private final ErrorDetails errorDetails;
    private final HttpStatus statusCode;

    public RemoteServiceException(String message, ErrorDetails errorDetails, HttpStatus statusCode) {
        super(message);
        this.errorDetails = errorDetails;
        this.statusCode = statusCode;
    }

    // 提供getter方法获取错误详情
    public ErrorDetails getErrorDetails() { return errorDetails; }
    public HttpStatus getStatusCode() { return statusCode; }
}

步骤2:配置RestTemplate使用自定义错误处理器

别每次调用都新建RestTemplate,这是不良实践!应该把它配置成Bean统一注入:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setErrorHandler(new CustomResponseErrorHandler());
        return restTemplate;
    }
}

步骤3:在Controller中使用配置好的RestTemplate

@RestController
public class YourController {
    private final RestTemplate restTemplate;

    // 构造注入配置好的RestTemplate实例
    public YourController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @PostMapping
    public ResponseEntity doSmth(@RequestBody String json){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String url = "...";
        HttpEntity<String> requestEntity = new HttpEntity<>("body", headers);
        
        try {
            URI location = restTemplate.postForLocation(url, requestEntity);
            return ResponseEntity.created(location).build();
        } catch (RemoteServiceException e) {
            // 这里就能拿到对方返回的具体ErrorDetails了
            ErrorDetails errorDetails = e.getErrorDetails();
            // 直接返回错误详情给客户端,或者做其他业务处理
            return new ResponseEntity<>(errorDetails, e.getStatusCode());
        }
    }
}

方案二:使用RestTemplate.exchange方法,统一处理成功/失败响应

postForLocation和postForObject都是exchange方法的封装,exchange更灵活——它不会自动抛出异常,而是返回包含完整响应信息(状态码、headers、body)的ResponseEntity,不管响应状态码是什么。这样你可以同时处理成功时获取location、失败时解析ErrorDetails的场景。

代码示例:

@PostMapping
public ResponseEntity doSmth(@RequestBody String json){
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    String url = "...";
    HttpEntity<String> requestEntity = new HttpEntity<>("body", headers);
    
    // 使用exchange方法,指定请求方式和响应体类型
    ResponseEntity<String> response = restTemplate.exchange(
            url,
            HttpMethod.POST,
            requestEntity,
            String.class
    );
    
    if (response.getStatusCode().is2xxSuccessful()) {
        // 成功时从headers中获取location
        URI location = response.getHeaders().getLocation();
        return ResponseEntity.created(location).build();
    } else {
        // 失败时解析响应体为ErrorDetails
        ObjectMapper objectMapper = new ObjectMapper();
        ErrorDetails errorDetails = objectMapper.readValue(response.getBody(), ErrorDetails.class);
        return new ResponseEntity<>(errorDetails, response.getStatusCode());
    }
}

如果想直接把响应体绑定到ErrorDetails,可以调整exchange的响应类型,注意成功时响应体为空的情况:

ResponseEntity<ErrorDetails> response = restTemplate.exchange(
        url,
        HttpMethod.POST,
        requestEntity,
        ErrorDetails.class
);

if (response.getStatusCode().is2xxSuccessful()) {
    URI location = response.getHeaders().getLocation();
    return ResponseEntity.created(location).build();
} else {
    return new ResponseEntity<>(response.getBody(), response.getStatusCode());
}

补充说明

  • 之前用postForObject拿不到location,是因为postForObject的设计目标就是只返回响应体,headers不会被暴露,只有exchange会返回完整的ResponseEntity包含headers信息。
  • 尽量不要每次调用都新建RestTemplate,统一配置成Bean可以更好地管理错误处理器、消息转换器等组件。

内容的提问来源于stack exchange,提问作者Donatello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:07:35