Actuator APIs (Logger for example) are using

Map deserializer when JSON is received and ObjectMapper when it is send. Which causes mismatched messages.

In my case I want ObectMapper to be configured with snake case and keep Maps unchanged. If I do this I have to use lower camel notation on my POST/PUT methods and snake case in GETs.

Comment From: wilkinsona

Thanks for the report, but I'm not sure that I have understood the problem. The Actuator uses an ObjectMapper instance for JSON serialisation and deserialisation. Are you observing a difference when mapping a POJO vs a Map, perhaps? To remove any doubt, can you please provide a minimal example that reproduces the problem? You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

Comment From: mkarasik

Sorry for not clear explanation. I am not too familiar with Spring terminology. I have the following ObjectMapper configuration in my app

 WebMvcConfigurer

@Override
 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        for (var converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                var objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
                objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
            }
        }
    }

It is probably not perfect code but that is what I have.

After doing this I found out that actuator Logger endpoint returns 'snake case' JSONs in GET requests but still require lower case camel notation in POST.

I spent some time in debugger and found that somewhere deep inside Spring code it is using Map deserializer to extract request parameters.

It is coming from

ServletWebOperationAdapter

@Override
public Object handle(HttpServletRequest request,
    @RequestBody(required = false) Map<String, String> body) {

you can see that body is already presented as Map here, so further processing in ReflectiveOperationInvoker.resolveArguments can't resolve arguments if they are sent in snake case.

Comment From: wilkinsona

This is Jackson's standard behaviour. It treats Map and POJO deserialisation differently and you have only configured its POJO deserialisation behaviour. If you want to customise the map key deserialisation, you'll have to configure Jackson with a custom key deserializer to convert the received snake_case into the required camelCase. You can do so with a SimpleModule that's exposed as a bean:

@Bean
Module customKeyDeserializer() {
    SimpleModule module = new SimpleModule();
    module.addKeyDeserializer(String.class, new KeyDeserializer() {

        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
            StringBuilder result = new StringBuilder();
            char previous = 0;
            for (char c : key.toCharArray()) {
                if (c != '_') {
                    result.append(previous == '_' ? Character.toUpperCase(c) : c);
                }
                previous = c;
            }
            return result.toString();
        }

    });
    return module;
}

Comment From: wilkinsona

See also https://github.com/spring-projects/spring-boot/issues/22950 for a somewhat similar problem with the serialisation of error responses and #20291 for providing an Actuator-specific ObjectMapper.

I'm going to close this one. Generally speaking we don't recommend try to customise the form of the JSON that the actuator accepts and produces. That's not easy right now as the ObjectMapper is shared with the application. #20291 will address that. In the meantime, the approach shown above should be used if you want to change the format of keys that the actuator consumes.

Comment From: mkarasik

Yes, I configured it differently and I want them to be different.

My complain is that actuator APIs are processed asymmetric: input is de-serialized through Map and output through POJO. I can try to configure custom map deserializer for Actuator endpoints only (not sure if this is possible at all) but I still think this is actuator design problem, not my configuration issue.