Spring @ExceptionHandler媒体类型不兼容致500错误的解决问询
问题原因
当客户端请求的Accept头为application/octet-stream且实体不存在时,你的全局异常处理器返回的是JSON格式的ErrorResponse,但Spring MVC会校验响应媒体类型是否匹配客户端的Accept要求。由于客户端明确只接受二进制流,Spring找不到兼容的响应转换器,会抛出HttpMediaTypeNotAcceptableException,最终导致服务返回500错误而非预期的404。
解决方案
方案一:全局异常处理器支持多媒体类型(推荐)
修改异常处理器方法,添加produces属性声明支持的媒体类型,并根据请求的Accept头返回对应格式的响应:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import java.nio.charset.StandardCharsets; @ControllerAdvice public class ResponseExceptionHandler { @ExceptionHandler(EntityNotFoundException.class) @GetMapping(produces = {"application/json", "application/octet-stream"}) public ResponseEntity<?> handleEntityNotFoundException(HttpServletRequest request, EntityNotFoundException e) { String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); if (MediaType.APPLICATION_OCTET_STREAM_VALUE.equals(acceptHeader)) { // 返回二进制格式错误信息 byte[] errorBytes = e.getMessage().getBytes(StandardCharsets.UTF_8); return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(errorBytes); } else { // 返回JSON格式错误信息 return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) .body(new ErrorResponse().error(e.getMessage())); } } // 兜底处理媒体类型不匹配异常 @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) public ResponseEntity<byte[]> handleMediaTypeNotAcceptable() { byte[] errorBytes = "Requested media type not supported".getBytes(StandardCharsets.UTF_8); return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(errorBytes); } }
方案二:强制返回JSON格式错误
如果业务允许忽略客户端的Accept要求,可在异常处理器中强制指定JSON媒体类型,同时处理媒体类型不匹配的异常:
@ControllerAdvice public class ResponseExceptionHandler { @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity<ErrorResponse> handleEntityNotFoundException(EntityNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) .body(new ErrorResponse().error(e.getMessage())); } @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) public ResponseEntity<ErrorResponse> handleMediaTypeNotAcceptable() { return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) .body(new ErrorResponse().error("Entity not found")); } }
方案三:端点内提前处理媒体类型匹配
在下载端点方法中,提前判断实体是否存在,并根据Accept头直接返回对应格式的响应,避免抛出异常后出现媒体类型冲突:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.springframework.core.io.ClassPathResource; import java.nio.charset.StandardCharsets; @RestController public class ResourceController { @GetMapping(value = "/{id}", produces = {"application/json", "application/octet-stream"}) public ResponseEntity<?> download(@PathVariable final int id, @RequestHeader(HttpHeaders.ACCEPT) String accept) { if (id % 2 == 0) { if (MediaType.APPLICATION_OCTET_STREAM_VALUE.equals(accept)) { byte[] errorBytes = "Entity not found".getBytes(StandardCharsets.UTF_8); return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(errorBytes); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) .body(new ErrorResponse().error("Entity not found")); } } return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(new ClassPathResource("application.properties")); } }
方案选择
方案一最符合REST设计规范,既能根据客户端需求返回对应格式的错误信息,又通过全局异常处理器实现了代码复用,是最优解。
内容的提问来源于stack exchange,提问作者MelleD
相关产品推荐
相关产品推荐

