diff --git a/conjure-java-jackson-optimizations/build.gradle b/conjure-java-jackson-optimizations/build.gradle index a90f820eb..f7183190e 100644 --- a/conjure-java-jackson-optimizations/build.gradle +++ b/conjure-java-jackson-optimizations/build.gradle @@ -3,4 +3,5 @@ apply plugin: 'com.palantir.revapi' dependencies { api "com.fasterxml.jackson.core:jackson-databind" + implementation "com.fasterxml.jackson.module:jackson-module-blackbird" } diff --git a/conjure-java-jackson-optimizations/src/main/java/com/palantir/conjure/java/jackson/optimizations/ObjectMapperOptimizations.java b/conjure-java-jackson-optimizations/src/main/java/com/palantir/conjure/java/jackson/optimizations/ObjectMapperOptimizations.java index 1208614fc..5b9a92094 100644 --- a/conjure-java-jackson-optimizations/src/main/java/com/palantir/conjure/java/jackson/optimizations/ObjectMapperOptimizations.java +++ b/conjure-java-jackson-optimizations/src/main/java/com/palantir/conjure/java/jackson/optimizations/ObjectMapperOptimizations.java @@ -16,6 +16,7 @@ package com.palantir.conjure.java.jackson.optimizations; +import com.fasterxml.jackson.module.blackbird.BlackbirdModule; import java.util.List; /** @@ -25,11 +26,29 @@ */ public final class ObjectMapperOptimizations { + // Blackbird generates classes at runtime via LambdaMetafactory, which native images cannot do. + private static final boolean NATIVE_IMAGE = System.getProperty("org.graalvm.nativeimage.imagecode") != null; + + /** Equivalent to {@link #createModules(boolean) createModules(false)}: no optimization modules. */ public static List extends com.fasterxml.jackson.databind.Module> createModules() { - // At one point this conditionally returned List.of(new AfterburnerModule), however afterburner is not - // supported on any supported LTS Java release anymore, and the Blackbird alternative incurs a memory - // leak (https://github.com/FasterXML/jackson-modules-base/issues/147). - return List.of(); + return createModules(false); + } + + /** + * Optionally registers the Blackbird module, which speeds up (de)serialization by generating accessors at + * runtime rather than using reflection. At one point this returned an {@code AfterburnerModule}, however + * afterburner is not supported on any supported LTS Java release anymore. + * + *
Blackbird generates a class per accessor via {@code LambdaMetafactory}. This is bounded and safe when a + * service reuses a small set of long-lived {@code ObjectMapper}s, but creating mappers per-request leaks + * compressed class space (https://github.com/FasterXML/jackson-modules-base/issues/147), so it is opt-in. + * Optimizations are always disabled within native images, where runtime class generation is unsupported. + */ + public static List extends com.fasterxml.jackson.databind.Module> createModules(boolean useBlackbird) { + if (!useBlackbird || NATIVE_IMAGE) { + return List.of(); + } + return List.of(new BlackbirdModule()); } private ObjectMapperOptimizations() {} diff --git a/conjure-java-jackson-serialization/src/main/java/com/palantir/conjure/java/serialization/ObjectMappers.java b/conjure-java-jackson-serialization/src/main/java/com/palantir/conjure/java/serialization/ObjectMappers.java index f1a906654..f21903472 100644 --- a/conjure-java-jackson-serialization/src/main/java/com/palantir/conjure/java/serialization/ObjectMappers.java +++ b/conjure-java-jackson-serialization/src/main/java/com/palantir/conjure/java/serialization/ObjectMappers.java @@ -50,7 +50,12 @@ private ObjectMappers() {} * */ public static JsonMapper newClientJsonMapper() { - return withDefaultModules(JsonMapper.builder(jsonFactory())) + return newClientJsonMapper(false); + } + + /** Like {@link #newClientJsonMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static JsonMapper newClientJsonMapper(boolean useOptimizations) { + return withDefaultModules(JsonMapper.builder(jsonFactory()), useOptimizations) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -65,7 +70,12 @@ public static JsonMapper newClientJsonMapper() { * */ public static CBORMapper newClientCborMapper() { - return withDefaultModules(CBORMapper.builder(cborFactory())) + return newClientCborMapper(false); + } + + /** Like {@link #newClientCborMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static CBORMapper newClientCborMapper(boolean useOptimizations) { + return withDefaultModules(CBORMapper.builder(cborFactory()), useOptimizations) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -81,7 +91,12 @@ public static CBORMapper newClientCborMapper() { * */ public static SmileMapper newClientSmileMapper() { - return withDefaultModules(SmileMapper.builder(smileFactory())) + return newClientSmileMapper(false); + } + + /** Like {@link #newClientSmileMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static SmileMapper newClientSmileMapper(boolean useOptimizations) { + return withDefaultModules(SmileMapper.builder(smileFactory()), useOptimizations) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -96,7 +111,12 @@ public static SmileMapper newClientSmileMapper() { * */ public static JsonMapper newServerJsonMapper() { - return withDefaultModules(JsonMapper.builder(jsonFactory())) + return newServerJsonMapper(false); + } + + /** Like {@link #newServerJsonMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static JsonMapper newServerJsonMapper(boolean useOptimizations) { + return withDefaultModules(JsonMapper.builder(jsonFactory()), useOptimizations) .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -111,7 +131,12 @@ public static JsonMapper newServerJsonMapper() { * */ public static CBORMapper newServerCborMapper() { - return withDefaultModules(CBORMapper.builder(cborFactory())) + return newServerCborMapper(false); + } + + /** Like {@link #newServerCborMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static CBORMapper newServerCborMapper(boolean useOptimizations) { + return withDefaultModules(CBORMapper.builder(cborFactory()), useOptimizations) .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -127,7 +152,12 @@ public static CBORMapper newServerCborMapper() { * */ public static SmileMapper newServerSmileMapper() { - return withDefaultModules(SmileMapper.builder(smileFactory())) + return newServerSmileMapper(false); + } + + /** Like {@link #newServerSmileMapper()}, optionally enabling Jackson optimization modules (Blackbird). */ + public static SmileMapper newServerSmileMapper(boolean useOptimizations) { + return withDefaultModules(SmileMapper.builder(smileFactory()), useOptimizations) .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @@ -170,13 +200,22 @@ public static ObjectMapper newSmileServerObjectMapper() { *