Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ TIP: Actuator is supported natively with Spring MVC, Spring WebFlux, and Jersey.
If both Jersey and Spring MVC are available, Spring MVC is used.

NOTE: Jackson is a required dependency in order to get the correct JSON responses as documented in the xref:api:rest/actuator/index.adoc[API documentation].
Jackson 3 should be used for Spring MVC and Spring WebFlux.
Jersey does not yet have a Jackson 3 module, so you will need to use Jackson 2.
Jackson 3 should be used for Spring MVC, Spring WebFlux, and Jersey.



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,14 @@ To avoid this limitation, the `packages` method should not be used, and endpoint

For more advanced customizations, you can also register an arbitrary number of beans that implement javadoc:org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer[].

Jersey uses Jackson 3 for JSON serialization and deserialization by default.
The auto-configured javadoc:tools.jackson.databind.json.JsonMapper[] is registered with Jersey and can be customized using `spring.jackson.*` properties or javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans.
See xref:features/json.adoc#features.json.jackson[] for more details.

To use the deprecated Jackson 2 support, set configprop:spring.jersey.preferred-json-mapper[] to `jackson2`.
Jersey then uses the auto-configured Jackson 2 `ObjectMapper`, including `spring.jackson2.*` settings and Jackson 2 customizations.
Both Jackson versions are included in the starter; dependency exclusions are not required to select a mapper.

All the registered endpoints should be a javadoc:org.springframework.stereotype.Component[format=annotation] with HTTP resource annotations (`@GET` and others), as shown in the following example:

include-code::MyEndpoint[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ dependencies {
testImplementation("net.minidev:json-smart")
testImplementation("org.springframework.security:spring-security-web")
testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") // Required for Jersey
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@
import java.util.Collection;
import java.util.HashSet;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.ext.ContextResolver;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jackson3.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.servlet.ServletContainer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;

import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
Expand Down Expand Up @@ -61,6 +61,7 @@
* Integration tests for web endpoints exposed using Jersey.
*
* @author Andy Wilkinson
* @author Kristoffer Larsen Hopland
* @see JerseyEndpointResourceFactory
*/
class JerseyWebEndpointIntegrationTests
Expand Down Expand Up @@ -121,7 +122,7 @@ ResourceConfig resourceConfig(Environment environment, WebEndpointDiscoverer end
new EndpointLinksResolver(endpointDiscoverer.getEndpoints()), StringUtils.hasText(endpointPath));
resourceConfig.registerResources(new HashSet<>(resources));
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(new ObjectMapperContextResolver(new ObjectMapper()), ContextResolver.class);
resourceConfig.register(new JsonMapperContextResolver(JsonMapper.builder().build()), ContextResolver.class);
return resourceConfig;
}

Expand Down Expand Up @@ -154,17 +155,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

}

private static final class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static final class JsonMapperContextResolver implements ContextResolver<JsonMapper> {

private final ObjectMapper objectMapper;
private final JsonMapper jsonMapper;

private ObjectMapperContextResolver(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
private JsonMapperContextResolver(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}

@Override
public ObjectMapper getContext(Class<?> type) {
return this.objectMapper;
public JsonMapper getContext(Class<?> type) {
return this.jsonMapper;
}

}
Expand Down
4 changes: 3 additions & 1 deletion module/spring-boot-jersey/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
api("org.glassfish.jersey.core:jersey-server")
api("org.glassfish.jersey.ext:jersey-spring6")
api("org.glassfish.jersey.media:jersey-media-json-jackson")
api("org.glassfish.jersey.media:jersey-media-json-jackson3")

compileOnly("jakarta.servlet:jakarta.servlet-api")
compileOnly("com.google.code.findbugs:jsr305")
Expand All @@ -40,6 +41,7 @@ dependencies {
optional(project(":core:spring-boot-autoconfigure"))
optional(project(":module:spring-boot-actuator-autoconfigure"))
optional(project(":module:spring-boot-health"))
optional(project(":module:spring-boot-jackson"))
optional(project(":module:spring-boot-jackson2"))
optional(project(":module:spring-boot-micrometer-metrics"))
optional(project(":module:spring-boot-micrometer-observation"))
Expand All @@ -48,7 +50,7 @@ dependencies {

testFixturesApi(testFixtures(project(":module:spring-boot-actuator")))
testFixturesImplementation(project(":module:spring-boot-tomcat"))
testFixturesRuntimeOnly(project(":module:spring-boot-jackson2"))
testFixturesRuntimeOnly(project(":module:spring-boot-jackson"))

testImplementation(project(":core:spring-boot-test"))
testImplementation(project(":module:spring-boot-restclient"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import jakarta.ws.rs.ext.ContextResolver;
import jakarta.xml.bind.annotation.XmlElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spring.SpringComponentProvider;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
Expand All @@ -43,15 +39,13 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.DynamicRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
Expand All @@ -66,9 +60,11 @@
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Kristoffer Larsen Hopland
* @since 4.0.0
*/
@AutoConfiguration(afterName = { "org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration" })
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
"org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration" })
@ConditionalOnClass({ SpringComponentProvider.class, ServletRegistration.class })
@ConditionalOnBean(type = "org.glassfish.jersey.server.ResourceConfig")
@ConditionalOnWebApplication(type = Type.SERVLET)
Expand Down Expand Up @@ -175,59 +171,4 @@ public void onStartup(ServletContext servletContext) throws ServletException {

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JacksonFeature.class)
@ConditionalOnSingleCandidate(com.fasterxml.jackson.databind.ObjectMapper.class)
static class Jackson2ResourceConfigCustomizerConfiguration {

@Bean
ResourceConfigCustomizer jacksonResourceConfigCustomizer(
com.fasterxml.jackson.databind.ObjectMapper objectMapper) {
return (ResourceConfig config) -> {
config.register(JacksonFeature.class);
config.register(new ObjectMapperContextResolver(objectMapper), ContextResolver.class);
};
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector.class,
XmlElement.class })
static class JaxbJackson2ObjectMapperCustomizerConfiguration {

@Autowired
void addJaxbAnnotationIntrospector(com.fasterxml.jackson.databind.ObjectMapper objectMapper) {
com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector jaxbAnnotationIntrospector = new com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector(
objectMapper.getTypeFactory());
objectMapper.setAnnotationIntrospectors(
createPair(objectMapper.getSerializationConfig(), jaxbAnnotationIntrospector),
createPair(objectMapper.getDeserializationConfig(), jaxbAnnotationIntrospector));
}

private com.fasterxml.jackson.databind.AnnotationIntrospector createPair(
com.fasterxml.jackson.databind.cfg.MapperConfig<?> config,
com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector jaxbAnnotationIntrospector) {
return com.fasterxml.jackson.databind.AnnotationIntrospector.pair(config.getAnnotationIntrospector(),
jaxbAnnotationIntrospector);
}

}

private static final class ObjectMapperContextResolver
implements ContextResolver<com.fasterxml.jackson.databind.ObjectMapper> {

private final com.fasterxml.jackson.databind.ObjectMapper objectMapper;

private ObjectMapperContextResolver(com.fasterxml.jackson.databind.ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
public com.fasterxml.jackson.databind.ObjectMapper getContext(Class<?> type) {
return this.objectMapper;
}

}

}

}
Loading