spring-projects / spring-projects/spring-boot

Auto-configure Jersey with a JacksonJaxbXMLProvider when jackson-jaxrs-xml-provider is on the classpath

Open
#31,924 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: blocked type: enhancement
Dominant language
Java
Stars
81.5k
Forks
42.7k
Avg merge
2d 4h
Merged PRs (30d)
65

Description

Problem

Spring Boot has fantastic support for Jackson. When using Jackson for JSON serialization/deserialization--especially with spring-mvc--it's easy to configure Jackson through the spring.jackson.* properties (thanks autoconfigure!).

Where this solution begins to fall short is when trying to use Jersey instead of Spring MVC, with Jackson for XML marshalling/unmarshalling. Ideally, the framework should expose a configured XmlMapper bean, or provide an easy way to build an XmlMapper that is configured, which we can then pass to the Jackson JAXB provider. Unfortunately I can't find a way to make that work cleanly.

Here's what I have for configuration:

@Configuration
@Slf4j
public class JerseyResourceSpringConfig {

    @Bean
    public ResourceConfig jerseyResourceConfig() {
        final var resourceConfig = new ResourceConfig();
        // ...
        resourceConfig.register(JacksonJaxbXMLProvider.class);
        return resourceConfig;
    }
}

It took me a while to figure out the spring.jackson.* properties weren't being respected, it wasn't immediately obvious (maybe some documentation enhancements would help). Internally Jersey will build an XmlMapper with its own defaults but they don't respect spring.jackson.* properties, which is obvious now given that Jersey doesn't really integrate with Spring in this way.

Alternatively it's possible to do something like this instead:

@Configuration
@Slf4j
public class JerseyResourceSpringConfig {

    @Bean
    public ResourceConfig jerseyResourceConfig() {
        final var resourceConfig = new ResourceConfig();
        // ...
        final var mapper = new XmlMapper();
        // configure mapper
        resourceConfig.register(new JacksonJaxbXMLProvider(mapper, JacksonJaxbXMLProvider.DEFAULT_ANNOTATIONS));
        return resourceConfig;
    }
}

The tricky part is configuring this XmlMapper correctly from those spring.jackson.* properties. From what I've seen there's no elegant way to do this in this specific configuration. Taking a look at JacksonAutoConfiguration, an ObjectMapper bean is created and some other goodies, and Jackson2ObjectMapperBuilder supports building XmlMapper, but manually exposing an XmlMapper bean doesn't seem ideal.

Here's what worked for me:

    @Bean
    public ResourceConfig jerseyResourceConfig(final JacksonJaxbXMLProvider provider) {
        final var resourceConfig = new ResourceConfig();
        // ...
        resourceConfig.register(provider);
        return resourceConfig;
    }

    @Bean
    public JacksonJaxbXMLProvider provider(final Jackson2ObjectMapperBuilder builder) {
        final var mapper = (XmlMapper) builder.createXmlMapper(true)
                .findModulesViaServiceLoader(true)
                .build();
        return new JacksonJaxbXMLProvider(mapper, JacksonJaxbXMLProvider.DEFAULT_ANNOTATIONS);
    }

It's not clear to me why I need to find jackson modules through the service loader.. Wouldn't the builder be configured to do this? Can someone explain why this might be needed here?

Proposed Solution

One option is to update the existing JacksonAutoConfiguration to register a configured XmlMapper bean, perhaps if XmlMapper is on the classpath. It could leverage all of the existing autoconfiguration!

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	static class JacksonObjectMapperConfiguration {

		@Bean
		@Primary
		@ConditionalOnMissingBean
		ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(false).build();
		}

                @Bean
		@Primary
		@ConditionalOnMissingBean
		XmlMapper jacksonXmlMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(true).build();
		}
	}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading JacksonAutoConfiguration and Jackson2ObjectMapperBuilder, focusing on how the existing ObjectMapper bean is created and how createXmlMapper(true) is used. Check the JacksonJaxbXMLProvider integration described in the issue. Done means a configured XML mapper or equivalent Jersey provider is automatically available when the relevant classpath dependency is present, with existing configuration preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.