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

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);
        }
    }
}

Configure the Converter

@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);
    }
}

When the converter is registered, the client must set Content-Type: application/fm for the request.

Parameter Object

public class Users {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "【name = " + this.name + ", age = " + this.age + "】";
    }
}

Controller

@RestController
@RequestMapping("/message")
public class MessageController {

    @PostMapping("/save")
    public Users save(@RequestBody Users user) {
        System.out.println("接受到内容:" + user);
        return user;
    }
}

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend DevelopmentSpring Boot@RequestBodyCustom 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

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.