From ded11293eef67c939ce0b73a698141b12c25662c Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Wed, 12 Nov 2025 14:30:58 -0800 Subject: [PATCH 1/9] fix Jackson Serializer deprecation --- .../ab/event/internal/serializer/JacksonSerializer.java | 6 +++--- .../ab/event/internal/serializer/JacksonSerializerTest.java | 4 ++-- java-quickstart/build.gradle | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index 6087b4cce..5c1d586fd 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019, 2025 Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,13 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; class JacksonSerializer implements Serializer { private ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy( - PropertyNamingStrategy.SNAKE_CASE); + PropertyNamingStrategies.SNAKE_CASE); public String serialize(T payload) { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index fb068e3ab..79974a986 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -42,7 +42,7 @@ public class JacksonSerializerTest { private JacksonSerializer serializer = new JacksonSerializer(); private ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy( - PropertyNamingStrategy.SNAKE_CASE); + PropertyNamingStrategies.SNAKE_CASE); @Test diff --git a/java-quickstart/build.gradle b/java-quickstart/build.gradle index a58fb090e..ef86b3045 100644 --- a/java-quickstart/build.gradle +++ b/java-quickstart/build.gradle @@ -4,7 +4,11 @@ dependencies { implementation project(':core-api') implementation project(':core-httpclient-impl') - implementation group: 'com.google.code.gson', name: 'gson', version: gsonVersion + // implementation group: 'com.google.code.gson', name: 'gson', version: gsonVersion + implementation 'com.fasterxml.jackson.core:jackson-core:2.17.0' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' + implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: httpClientVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion From a860eeb9746d6cd20cebf9034d5694fe0f6d8904 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Thu, 13 Nov 2025 09:07:22 -0800 Subject: [PATCH 2/9] change to temurin --- .github/workflows/java.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 95e8ccf8d..7d53b4dc0 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -56,10 +56,10 @@ jobs: uses: actions/checkout@v4 - name: set up JDK ${{ matrix.jdk }} - uses: AdoptOpenJDK/install-jdk@v1 + uses: actions/setup-java@v4 with: - version: ${{ matrix.jdk }} - architecture: x64 + java-version: ${{ matrix.jdk }} + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew From 7d72e44556dabf0d585bf38966e7eb1bde5e073a Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Thu, 13 Nov 2025 09:12:28 -0800 Subject: [PATCH 3/9] use zulu --- .github/workflows/java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 7d53b4dc0..1328d542c 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -59,7 +59,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: ${{ matrix.jdk }} - distribution: 'temurin' + distribution: 'zulu' - name: Grant execute permission for gradlew run: chmod +x gradlew From 303d07fdd7e43d9df314f5cd233ea7c33b9e4e6a Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Thu, 13 Nov 2025 09:28:45 -0800 Subject: [PATCH 4/9] jdk to 8,11 --- .github/workflows/java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 1328d542c..bb5dad45d 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - jdk: [8, 9] + jdk: [8, 11] optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER] steps: - name: checkout From abcf41ae0e4f2171feedb90f8794a84c3e300546 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Thu, 13 Nov 2025 10:01:54 -0800 Subject: [PATCH 5/9] use temurin, 11 and 17 --- .github/workflows/java.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index bb5dad45d..7f9632834 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - jdk: [8, 11] + jdk: [11, 17] optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER] steps: - name: checkout @@ -59,7 +59,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: ${{ matrix.jdk }} - distribution: 'zulu' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew From 3b672238f70db071ce9fb20c74a7f7a2a09abd5e Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Mon, 1 Dec 2025 17:48:22 -0800 Subject: [PATCH 6/9] fix to support old jackson versions too --- README.md | 6 +-- .../serializer/JacksonSerializer.java | 46 +++++++++++++++++-- .../serializer/JacksonSerializerTest.java | 27 +++++++++-- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1a7370c43..e5d88473f 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ dependencies { compile 'com.optimizely.ab:core-api:{VERSION}' compile 'com.optimizely.ab:core-httpclient-impl:{VERSION}' // The SDK integrates with multiple JSON parsers, here we use Jackson. - compile 'com.fasterxml.jackson.core:jackson-core:2.7.1' - compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.1' - compile 'com.fasterxml.jackson.core:jackson-databind:2.7.1' + compile 'com.fasterxml.jackson.core:jackson-core:2.13.5' + compile 'com.fasterxml.jackson.core:jackson-annotations:2.13.5' + compile 'com.fasterxml.jackson.core:jackson-databind:2.13.5' } ``` diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index 5c1d586fd..2d6560e79 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -19,13 +19,51 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; class JacksonSerializer implements Serializer { - private ObjectMapper mapper = - new ObjectMapper().setPropertyNamingStrategy( - PropertyNamingStrategies.SNAKE_CASE); + private ObjectMapper mapper = createMapper(); + + /** + * Creates an ObjectMapper with snake_case naming strategy. + * Supports both Jackson 2.12+ (PropertyNamingStrategies) and earlier versions (PropertyNamingStrategy). + * Uses reflection to avoid compile-time dependencies on either API. + */ + static ObjectMapper createMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + Object namingStrategy = getSnakeCaseStrategy(); + + try { + // Use setPropertyNamingStrategy method (available in all versions) + objectMapper.getClass() + .getMethod("setPropertyNamingStrategy", + Class.forName("com.fasterxml.jackson.databind.PropertyNamingStrategy")) + .invoke(objectMapper, namingStrategy); + } catch (Exception e) { + throw new RuntimeException("Failed to set snake_case naming strategy", e); + } + + return objectMapper; + } + + /** + * Gets the snake case naming strategy, supporting both Jackson 2.12+ and earlier versions. + */ + private static Object getSnakeCaseStrategy() { + try { + // Try Jackson 2.12+ API first + Class strategiesClass = Class.forName("com.fasterxml.jackson.databind.PropertyNamingStrategies"); + return strategiesClass.getField("SNAKE_CASE").get(null); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + try { + // Fall back to Jackson 2.11 and earlier (deprecated but compatible) + Class strategyClass = Class.forName("com.fasterxml.jackson.databind.PropertyNamingStrategy"); + return strategyClass.getField("SNAKE_CASE").get(null); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException ex) { + throw new RuntimeException("Unable to find snake_case naming strategy in Jackson", ex); + } + } + } public String serialize(T payload) { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index 79974a986..139e0db66 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -36,14 +35,34 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; public class JacksonSerializerTest { private JacksonSerializer serializer = new JacksonSerializer(); - private ObjectMapper mapper = - new ObjectMapper().setPropertyNamingStrategy( - PropertyNamingStrategies.SNAKE_CASE); + private ObjectMapper mapper = JacksonSerializer.createMapper(); + @Test + public void createMapperSucceeds() { + // Verify that createMapper() successfully creates an ObjectMapper with snake_case naming + // This tests that the reflection logic works for the current Jackson version + ObjectMapper testMapper = JacksonSerializer.createMapper(); + assertNotNull("Mapper should be created successfully", testMapper); + + // Verify snake_case naming by serializing a simple object + class TestObject { + @SuppressWarnings("unused") + public String getMyFieldName() { return "test"; } + } + + try { + String json = testMapper.writeValueAsString(new TestObject()); + assertTrue("Should use snake_case naming", json.contains("my_field_name")); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize with snake_case naming", e); + } + } @Test public void serializeImpression() throws IOException { From 5815a16bf48d76684e36be9400a5fae8f31ca89d Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Tue, 2 Dec 2025 16:05:50 -0800 Subject: [PATCH 7/9] add jvm 8 test --- .github/workflows/java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 7f9632834..ab73e8b83 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - jdk: [11, 17] + jdk: [8, 11, 17] optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER] steps: - name: checkout From aa3b05c7fbc3ebd591ec5fb43c40cd9f2e2aae25 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Tue, 2 Dec 2025 16:07:49 -0800 Subject: [PATCH 8/9] clean up --- .github/workflows/java.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index ab73e8b83..2438cb3d3 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -49,7 +49,8 @@ jobs: strategy: fail-fast: false matrix: - jdk: [8, 11, 17] + # github not support JVM 8 anymore + jdk: [11, 17] optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER] steps: - name: checkout From 70c1d099e3c8c4095045a9ee957e313bd183ff95 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Tue, 2 Dec 2025 16:39:51 -0800 Subject: [PATCH 9/9] clean up --- .../event/internal/serializer/JacksonSerializer.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index 2d6560e79..1467a0fa4 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -32,17 +32,7 @@ class JacksonSerializer implements Serializer { static ObjectMapper createMapper() { ObjectMapper objectMapper = new ObjectMapper(); Object namingStrategy = getSnakeCaseStrategy(); - - try { - // Use setPropertyNamingStrategy method (available in all versions) - objectMapper.getClass() - .getMethod("setPropertyNamingStrategy", - Class.forName("com.fasterxml.jackson.databind.PropertyNamingStrategy")) - .invoke(objectMapper, namingStrategy); - } catch (Exception e) { - throw new RuntimeException("Failed to set snake_case naming strategy", e); - } - + objectMapper.setPropertyNamingStrategy((com.fasterxml.jackson.databind.PropertyNamingStrategy) namingStrategy); return objectMapper; }