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

Spring REST WebService校验失效,抛出HttpMessageNotWritableException异常

解决Spring REST API参数校验时的HttpMessageNotWritableException异常

嘿,我刚看完你的问题,这个异常其实很好定位和解决!

问题回顾

你在给Spring REST API加参数校验的时候,碰到了这个头疼的异常:

Apr 10, 2020 12:05:26 PM org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver doResolveHandlerMethodException
WARNING: Failed to invoke @ExceptionHandler method: public com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation com.luv2code.springdemo.exceptionhandling.GlobalExceptionHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException)
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation

先把你贴的相关代码整理出来:

实体类Customer

@Entity
@Table(name="customer")
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="first_name")
    @NotNull(message = "Firstname is necessary")
    @Size(min=1,message="This field is required")
    private String firstName;

    @Column(name="last_name")
    @NotNull(message = "Lastname is necessary")
    @Size(min=1,message="This field is required")
    private String lastName;

    @Column(name="email")
    private String email;

    // getters and setters
}

错误响应类RestFieldError

public class RestFieldError {
    private String field;
    private String message;

    public RestFieldError() { }

    // getters and setters
}

错误响应包装类RestFieldErrorValidation

public class RestFieldErrorValidation {
    private List<RestFieldError> fieldErrors = new ArrayList<>();

    public RestFieldErrorValidation() { }

    public void addFieldError(String path, String message) {
        RestFieldError error = new RestFieldError(path, message);
        fieldErrors.add(error);
    }
}

RestController代码

@RestController
@RequestMapping("/api")
public class CustomerRestController {
    @Autowired
    private CustomerService customerService;

    @InitBinder
    public void initBinder(WebDataBinder dataBinder) {
        System.out.println("Entered init binder");
        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
        dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
    }

    @PostMapping("/customers")
    @ResponseBody
    public Customer addCustomer(@Valid @RequestBody Customer theCustomer) {
        System.out.println("theCustomer :"+theCustomer.getFirstName());
        theCustomer.setId(0);
        customerService.saveCustomer(theCustomer);
        return theCustomer;
    }
}

全局异常处理器GlobalExceptionHandler

@ControllerAdvice
public class GlobalExceptionHandler {
    private MessageSource messageSource;

    @Autowired
    public GlobalExceptionHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public RestFieldErrorValidation processValidationError(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        return processFieldErrors(fieldErrors);
    }

    private RestFieldErrorValidation processFieldErrors(List<FieldError> fieldErrors) {
        RestFieldErrorValidation dto = new RestFieldErrorValidation();
        for (FieldError fieldError: fieldErrors) {
            String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
            dto.addFieldError(fieldError.getField(), localizedErrorMessage);
        }
        return dto;
    }

    private String resolveLocalizedErrorMessage(FieldError fieldError) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
        if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
            String[] fieldErrorCodes = fieldError.getCodes();
            localizedErrorMessage = fieldErrorCodes[0];
        }
        return localizedErrorMessage;
    }
}

问题根源

别急,这个异常的原因其实很简单:Spring没办法把你的RestFieldErrorValidation对象转成JSON响应!
具体原因有两个:

  1. RestFieldErrorValidation类里的fieldErrors属性没有对应的getter方法,Spring的消息转换器(比如Jackson)无法读取这个属性的值;
  2. RestFieldError类不仅缺少带参数的构造方法(你在addFieldError里直接调用了new RestFieldError(path, message),但原类里没有这个构造),也可能没有完整的getter方法,导致即使创建了对象,也无法被序列化。

修复方案

只需要修改两个错误响应类就行,非常简单:

  1. 完善RestFieldError类,补上带参数的构造和属性getter:
public class RestFieldError {
    private String field;
    private String message;

    public RestFieldError() { }

    // 补上这个带参数的构造,匹配你在addFieldError里的调用
    public RestFieldError(String field, String message) {
        this.field = field;
        this.message = message;
    }

    // 添加getter方法,让Jackson能读取属性值
    public String getField() {
        return field;
    }

    public String getMessage() {
        return message;
    }
}
  1. 给RestFieldErrorValidation类添加fieldErrors的getter:
public class RestFieldErrorValidation {
    private List<RestFieldError> fieldErrors = new ArrayList<>();

    public RestFieldErrorValidation() { }

    public void addFieldError(String path, String message) {
        RestFieldError error = new RestFieldError(path, message);
        fieldErrors.add(error);
    }

    // 新增这个getter,暴露fieldErrors属性给序列化器
    public List<RestFieldError> getFieldErrors() {
        return fieldErrors;
    }
}

额外检查点

  • 如果你是Spring Boot项目,spring-boot-starter-web已经自带了Jackson依赖,不用额外加;如果是普通Spring项目,记得引入Jackson的核心依赖(比如com.fasterxml.jackson.core:jackson-databind);
  • 可以测试一下参数校验的场景,看看返回的错误响应是不是符合你的预期,顺便验证GlobalExceptionHandler里的消息解析逻辑有没有问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 12:32:55