SpringBoot
ObjectMapper
Controller
JSON
Java

Is it possible to register Controller specific ObjectMapper in SpringBoot

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Spring Boot makes JSON handling feel automatic because it wires a global Jackson ObjectMapper into the MVC stack. That default is convenient, but it also raises a practical question: what if one controller needs different JSON rules from the rest of the application?

Short Answer

Yes, but not in the sense of attaching a private mapper to a controller annotation and letting Spring magically switch behavior. Spring MVC selects HttpMessageConverter instances at the application level. To get controller-specific behavior, you usually choose one of three patterns:

  • serialize explicitly with a secondary mapper,
  • register a custom converter tied to a custom media type, or
  • avoid multiple mappers entirely and use DTOs, @JsonView, or Jackson annotations.

Option 1: Inject a Secondary Mapper and Serialize Explicitly

This is the most direct and least surprising approach. Keep the default mapper for normal endpoints and inject a second mapper where special serialization is required.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import com.fasterxml.jackson.databind.PropertyNamingStrategies;
3import com.fasterxml.jackson.databind.SerializationFeature;
4import org.springframework.beans.factory.annotation.Qualifier;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.context.annotation.Primary;
8
9@Configuration
10public class JacksonConfig {
11
12    @Bean
13    @Primary
14    ObjectMapper defaultMapper() {
15        return new ObjectMapper();
16    }
17
18    @Bean
19    @Qualifier("legacyMapper")
20    ObjectMapper legacyMapper() {
21        ObjectMapper mapper = new ObjectMapper();
22        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
23        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
24        return mapper;
25    }
26}

Use it in the controller:

java
1import com.fasterxml.jackson.core.JsonProcessingException;
2import com.fasterxml.jackson.databind.ObjectMapper;
3import org.springframework.beans.factory.annotation.Qualifier;
4import org.springframework.http.MediaType;
5import org.springframework.http.ResponseEntity;
6import org.springframework.web.bind.annotation.GetMapping;
7import org.springframework.web.bind.annotation.RequestMapping;
8import org.springframework.web.bind.annotation.RestController;
9
10@RestController
11@RequestMapping("/api/legacy")
12public class LegacyController {
13
14    private final ObjectMapper legacyMapper;
15
16    public LegacyController(@Qualifier("legacyMapper") ObjectMapper legacyMapper) {
17        this.legacyMapper = legacyMapper;
18    }
19
20    @GetMapping(value = "/profile", produces = MediaType.APPLICATION_JSON_VALUE)
21    public ResponseEntity<String> profile() throws JsonProcessingException {
22        UserProfile profile = new UserProfile("Mark", "Qian");
23        return ResponseEntity.ok(legacyMapper.writeValueAsString(profile));
24    }
25}

This approach is explicit. When you read the controller, you immediately see that it uses custom JSON rules.

Option 2: Use a Custom Converter for a Specific Media Type

If you want automatic @ResponseBody conversion instead of returning a JSON String, create another MappingJackson2HttpMessageConverter and bind it to a custom media type.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import org.springframework.beans.factory.annotation.Qualifier;
3import org.springframework.context.annotation.Configuration;
4import org.springframework.http.MediaType;
5import org.springframework.http.converter.HttpMessageConverter;
6import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
7import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8
9import java.util.List;
10
11@Configuration
12public class WebConfig implements WebMvcConfigurer {
13
14    private final ObjectMapper legacyMapper;
15
16    public WebConfig(@Qualifier("legacyMapper") ObjectMapper legacyMapper) {
17        this.legacyMapper = legacyMapper;
18    }
19
20    @Override
21    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
22        MappingJackson2HttpMessageConverter converter =
23            new MappingJackson2HttpMessageConverter(legacyMapper);
24        converter.setSupportedMediaTypes(List.of(MediaType.valueOf("application/vnd.legacy+json")));
25        converters.add(0, converter);
26    }
27}

Then the controller opts in by its produced media type:

java
1import org.springframework.web.bind.annotation.GetMapping;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RestController;
4
5import java.util.List;
6
7@RestController
8@RequestMapping(value = "/api/v1", produces = "application/vnd.legacy+json")
9public class LegacyApiController {
10
11    @GetMapping("/orders")
12    public List<OrderDto> orders() {
13        return List.of(new OrderDto(1001L, "created"));
14    }
15}

This is useful when an entire API version has a distinct contract.

Option 3: Do Not Reach for Another Mapper Yet

Multiple mappers add complexity. If the difference is small, a second mapper may be the wrong tool. Consider these alternatives first:

  • '@JsonView when different endpoints expose different field subsets.'
  • endpoint-specific DTOs when the response contract differs structurally.
  • custom serializers for one or two unusual properties.
  • 'MappingJacksonValue when you want per-request filtering.'

In many codebases, DTOs are easier to test and easier to reason about than many partially overlapping mapper configurations.

What Spring Boot Does by Default

Spring Boot auto-configures one primary mapper and one or more JSON converters that use it. Those converters are shared infrastructure. That is why there is no built-in @UseObjectMapper("name") style switch on a controller.

The framework favors global configuration plus explicit opt-in patterns when an endpoint must behave differently.

Common Pitfalls

Mutating the global ObjectMapper inside a controller is dangerous. The mapper is normally shared, so runtime changes can leak into unrelated requests.

Using multiple mappers for small cosmetic differences creates maintenance debt. If you only need different field names or omitted fields, DTOs or @JsonView are usually cleaner.

Registering a custom converter without a distinct media type can affect other controllers unexpectedly. Make the routing rule explicit.

Returning raw JSON strings everywhere loses some Spring MVC conveniences, including clearer content negotiation and type-based behavior. Use explicit serialization sparingly.

Summary

  • Spring Boot does not provide a simple per-controller mapper switch by default.
  • The safest controller-specific pattern is injecting a secondary mapper and serializing explicitly.
  • For whole endpoint families, a custom media type plus custom converter is often cleaner.
  • If the differences are small, DTOs or @JsonView are usually better than multiple mappers.
  • Avoid mutating the shared global mapper during request handling.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.