Backend Development 8 min read

Create a Custom HttpMessageConverter in Spring Boot 2.6

This article demonstrates how to create a custom HttpMessageConverter in Spring Boot 2.6.12 to parse a simple “name:张三,age:20” string into a Users object, configure it in WebMvc, and explains the underlying Spring MVC request‑handling flow that invokes the converter.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Create a Custom HttpMessageConverter in Spring Boot 2.6

Environment: Spring Boot 2.6.12

Goal: implement a custom message format where the request body is a simple string like "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;
    }

    // Read and convert request body
    @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 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>

Configure 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 the converter is registered, the client must set Content-Type: application/fm for the request.

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>

Test request and response (images omitted) show that the custom format is correctly parsed and echoed.

Why the Converter Overrides These Methods

The @RequestBody annotation triggers RequestResponseBodyMethodProcessor , which eventually delegates to AbstractMessageConverterMethodArgumentResolver . The processing flow is:

DispatcherServlet → RequestMappingHandlerAdapter → invokeHandlerMethod → ServletInvocableHandlerMethod → invokeForRequest → getMethodArgumentValues → readWithMessageConverters.

During readWithMessageConverters the framework iterates over registered HttpMessageConverter instances. Our CustomHttpMessageConverter is selected because canRead returns true for the Users class and the application/fm media type.

The selected converter’s readInternal parses the raw string into a Users object, and writeInternal serializes the object back to the custom string format.

backend developmentSpring BootRequestBodyCustom ConverterHttpMessageConverter
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.