這個例外的含義就一句話:Spring 找不到能把這個 Java 物件寫進響應的
HttpMessageConverter。要麼你produces指定了一個沒配轉換器的媒體類型,要麼返回類型寫得像Object那樣寬,讓 Spring 沒法推斷。
Issue & Env(案發現場與環境)
- 現象:控制器方法執行正常,但返回結果階段拋 500,客戶端拿不到數據。
- 環境:Spring Boot 2.x / 3.x,
spring-boot-starter-web(預設帶 Jackson)。常見於指定了 XML 等非 JSON 輸出、或改動了內容協商設定的場景。
StackTrace(堆疊日誌)
服務端日誌裡的標準例外:
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.example.Student] with preset Content-Type 'null'
at org.springframework.http.converter.HttpMessageConverterConfigurer.chooseConverter(HttpMessageConverterConfigurer.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.writeWithMessageConverters(RequestResponseBodyMethodProcessor.java:195)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:182)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeAdapterInternal(RequestMappingHandlerAdapter.java:398)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:1004)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
Caused by: java.lang.IllegalArgumentException: 'resolver' must not be null
at org.springframework.util.Assert.notNull(Assert.java:204)
at org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter.writeTo(Jaxb2CollectionHttpMessageConverter.java:102)
... 14 more
也有另一種寫法(返回類型不明確時):
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.example.Student
at org.springframework.http.converter.HttpMessageConverterConfigurer.chooseConverter(HttpMessageConverterConfigurer.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.writeWithMessageConverters(RequestResponseBodyMethodProcessor.java:195)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:182)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeAdapterInternal(RequestMappingHandlerAdapter.java:398)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:1004)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
Caused by: java.lang.IllegalArgumentException: 'resolver' must not be null
at org.springframework.util.Assert.notNull(Assert.java:204)
at org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter.writeTo(Jaxb2CollectionHttpMessageConverter.java:102)
... 14 more
這兩種都是同一個根子:寫響應時沒找到合適的消息轉換器。
Reproduce(復現程式碼)
用 produces 指定一個沒註冊轉換器的媒體類型(比如 application/xml,但沒加 XML 轉換器):
@RestController
public class StudentController {
@GetMapping(value = "/student", produces = MediaType.APPLICATION_XML_VALUE)
public Student get() {
return new Student(1, "Tom");
}
}
Spring Boot 預設只帶 Jackson JSON 轉換器。啟動後訪問 /student,就拋上面那段例外。
Root Cause(根因分析)
Spring MVC 寫響應體時,會根據返回值類型和請求的 Accept / produces 媒體類型,從註冊好的 HttpMessageConverter 列表裡挑一個能寫的。挑不到,就拋 HttpMessageNotWritableException。
轉換器是自動設定註冊的:MappingJackson2HttpMessageConverter 只有在 classpath 裡有 Jackson 時才會註冊。你 produces = application/xml,但要寫 XML 得額外引入 jackson-dataformat-xml(以及 STAX),否則一個能寫 XML 的轉換器都沒有,內容協商直接落空。
還有一種隱蔽形態:控制器方法返回類型寫成 Object,Spring 得在運行時靠真實物件類型去匹配轉換器,一旦那個類型沒有對應轉換器,同樣失敗。spring-ai-ascend 的 issue #226 裡就是這種情況——返回類型寫 Object,某個 SDK 內部 POJO 沒有轉換器,Content-Type 被協商成 null。
The Fix(解決方案)
先確認你到底要輸出什麼格式。
輸出 JSON,就把 produces 改成 JSON,或乾脆去掉。有 Jackson 依賴時,MappingJackson2HttpMessageConverter 會自動兜底:
@GetMapping(value = "/student", produces = MediaType.APPLICATION_JSON_VALUE)
public Student get() {
return new Student(1, "Tom");
}
真要輸出 XML,補上依賴:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
返回類型儘量寫具體,別用 Object。需要封裝狀態碼和響應頭時,用 ResponseEntity:
@GetMapping("/student")
public ResponseEntity<Student> get() {
return ResponseEntity.ok(new Student(1, "Tom"));
}
具體返回類型加上 ResponseEntity,能讓 Spring 在編譯期就知道目標類型,減少運行時的內容協商歧義。
