How to Build a Custom HttpMessageConverter in Spring Boot 2.5
This guide explains how to implement a custom HttpMessageConverter in Spring Boot 2.5, covering the converter class, configuration, data model, controller, testing steps, and the internal request‑handling flow within the Spring MVC framework.
Environment: Spring Boot 2.5.12
Message Format
Input parameters: name:张三,age:20
Custom HttpMessageConverter
<code>public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private static Logger logger = LoggerFactory.getLogger(CustomHttpMessageConverter.class);
// Only support Users type
@Override
protected boolean supports(Class<?> clazz) {
return Users.class == clazz;
}
// Convert request body to Users instance
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
String content = inToString(inputMessage.getBody());
String[] keys = content.split(",");
Users instance = null;
try {
instance = (Users) clazz.newInstance();
} catch (Exception e1) {
e1.printStackTrace();
}
for (String key : keys) {
String[] vk = key.split(":");
try {
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (f.getName().equals(vk[0])) {
f.setAccessible(true);
Class<?> type = f.getType();
if (String.class == type) {
f.set(instance, vk[1]);
} else if (Integer.class == type) {
f.set(instance, Integer.parseInt(vk[1]));
}
break;
}
}
} catch (Exception e) {
logger.error("错误:{}", e);
}
}
return instance;
}
// Write response using object's toString()
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(t.toString().getBytes());
}
@Override
protected boolean canWrite(MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
private String inToString(InputStream is) {
byte[] buf = new byte[10 * 1024];
int leng = -1;
StringBuilder sb = new StringBuilder();
try {
while ((leng = is.read(buf)) != -1) {
sb.append(new String(buf, 0, leng));
}
return sb.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
</code>Registering the Converter
<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
CustomHttpMessageConverter messageConvert = new CustomHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(new MediaType("application", "fm"));
messageConvert.setSupportedMediaTypes(supportedMediaTypes);
converters.add(messageConvert);
WebMvcConfigurer.super.configureMessageConverters(converters);
}
}
</code>When configuring the converter, the supported media type is set to application/fm , so clients must send requests with this Content‑Type.
Parameter Object
<code>public class Users {
private String name;
private Integer age;
@Override
public String toString() {
return "【name = " + this.name + ", age = " + this.age + "】";
}
}
</code>Controller
<code>@RestController
@RequestMapping("/message")
public class MessageController {
@PostMapping("/save")
public Users save(@RequestBody Users user) {
System.out.println("接受到内容:" + user);
return user;
}
}
</code>Testing
Request example (Content‑Type: application/fm ) and corresponding response are shown in the images below.
Why These Methods Must Be Overridden
The @RequestBody annotation triggers the RequestResponseBodyMethodProcessor which is invoked by the DispatcherServlet . The processing flow passes through RequestMappingHandlerAdapter#handleInternal , then invokeHandlerMethod , and eventually reaches ServletInvocableHandlerMethod#invokeAndHandle . Inside this chain, AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters iterates over the registered HttpMessageConverter instances. Because we added CustomHttpMessageConverter to the converter list, it is selected, and its readInternal method parses the custom application/fm payload into a Users object. The writeInternal method performs the reverse operation for responses.
During debugging, the flow reaches the custom converter, confirming that the canRead check (inherited from the parent class) succeeds for the Users type, and the readInternal method processes the message content as expected.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.