From 1058674f5e47c8230053961fef11ae7fef98d7a3 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 11 Jun 2026 15:52:57 -0700 Subject: [PATCH 1/2] Fix #700, bad cbor/smile mapper SPI metadata --- .../tools.jackson.databind.ObjectMapper | 2 +- .../dataformat/cbor/ServiceLoader700Test.java | 58 +++++++++++++++++++ release-notes/CREDITS | 6 ++ release-notes/VERSION | 6 ++ .../tools.jackson.databind.ObjectMapper | 2 +- .../smile/ServiceLoader700Test.java | 48 +++++++++++++++ 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java create mode 100644 smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java diff --git a/cbor/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper b/cbor/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper index bce60816d..d7a5f6495 100644 --- a/cbor/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper +++ b/cbor/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper @@ -1 +1 @@ -tools.jackson.dataformat.cbor.databind.CBORMapper +tools.jackson.dataformat.cbor.CBORMapper diff --git a/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java b/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java new file mode 100644 index 000000000..505bb89cb --- /dev/null +++ b/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java @@ -0,0 +1,58 @@ +package tools.jackson.dataformat.cbor; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.*; + +// [dataformats-binary#700]: SPI file referenced wrong class name +// (`...cbor.databind.CBORMapper` instead of `...cbor.CBORMapper`), +// causing `ServiceConfigurationError` from `ServiceLoader`. +public class ServiceLoader700Test extends CBORTestBase +{ + private final static String SERVICE_FILE = + "META-INF/services/tools.jackson.databind.ObjectMapper"; + + @Test + public void testServiceFileClassNamesResolve() throws Exception + { + boolean foundCBORMapper = false; + // Enumerate every copy of the SPI file on the path (cbor, smile, etc.)... + Enumeration resources = getClass().getClassLoader().getResources(SERVICE_FILE); + assertTrue(resources.hasMoreElements(), + "Should find at least one `" + SERVICE_FILE + "` resource"); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + System.err.println("DEBUG url=" + url); + try (InputStream in = url.openStream()) { + BufferedReader r = new BufferedReader( + new InputStreamReader(in, StandardCharsets.UTF_8)); + String line; + while ((line = r.readLine()) != null) { + System.err.println("DEBUG line=" + line); + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Every class named in any SPI file must resolve to an `ObjectMapper`... + Class cls = Class.forName(line); + assertTrue(ObjectMapper.class.isAssignableFrom(cls), + "Class `" + line + "` (from " + url + ") is not an `ObjectMapper` subtype"); + if (cls == CBORMapper.class) { + foundCBORMapper = true; + } + } + } + } + assertTrue(foundCBORMapper, + "SPI file should list `" + CBORMapper.class.getName() + "`"); + } +} diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 744a983a1..2acaa1f0d 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -44,3 +44,9 @@ Shanchao Li (@tonghuaroot) * Reported #693: (avro) Incomplete number length validation in Avro decoder (for `BigDecimal`) (3.1.4) + +Juan Farré (@jafarre-bi) + +* Reported #700: (cbor, smile) `META-INF/services/tools.jackson.databind.ObjectMapper` + references wrong class name (`...databind.CBORMapper` / `...databind.SmileMapper`) + (3.1.5) diff --git a/release-notes/VERSION b/release-notes/VERSION index 3a3a0adac..7eb77d2f8 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -18,6 +18,12 @@ implementations) No changes since 3.1 +3.1.5 (not yet released) + +#700: (cbor, smile) `META-INF/services/tools.jackson.databind.ObjectMapper` + references wrong class name (`...databind.CBORMapper` / `...databind.SmileMapper`) + (reported by Juan F) + 3.1.4 (29-May-2026) #691: (cbor) Add parameterized tests covering all ASCII-optimization exit paths diff --git a/smile/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper b/smile/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper index 95a4fdeef..3a02fc3b4 100644 --- a/smile/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper +++ b/smile/src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper @@ -1 +1 @@ -tools.jackson.dataformat.smile.databind.SmileMapper +tools.jackson.dataformat.smile.SmileMapper diff --git a/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java b/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java new file mode 100644 index 000000000..a2c2e063e --- /dev/null +++ b/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java @@ -0,0 +1,48 @@ +package tools.jackson.dataformat.smile; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.*; + +// [dataformats-binary#700]: SPI file referenced wrong class name +// (`...smile.databind.SmileMapper` instead of `...smile.SmileMapper`), +// causing `ServiceConfigurationError` from `ServiceLoader`. +public class ServiceLoader700Test extends BaseTestForSmile +{ + private final static String SERVICE_FILE = + "META-INF/services/tools.jackson.databind.ObjectMapper"; + + @Test + public void testServiceFileClassNamesResolve() throws Exception + { + boolean foundSmileMapper = false; + try (InputStream in = getClass().getModule().getResourceAsStream(SERVICE_FILE)) { + assertNotNull(in, "Missing SPI resource: " + SERVICE_FILE); + BufferedReader r = new BufferedReader( + new InputStreamReader(in, StandardCharsets.UTF_8)); + String line; + while ((line = r.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Class named in SPI file must actually exist and be an `ObjectMapper`... + Class cls = Class.forName(line); + assertTrue(ObjectMapper.class.isAssignableFrom(cls), + "Class `" + line + "` is not an `ObjectMapper` subtype"); + if (cls == SmileMapper.class) { + foundSmileMapper = true; + } + } + } + assertTrue(foundSmileMapper, + "SPI file should list `" + SmileMapper.class.getName() + "`"); + } +} From 69cb64d1f443bccfe1535da04f994b45a01680bb Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 11 Jun 2026 15:57:14 -0700 Subject: [PATCH 2/2] Fix tests --- .../dataformat/cbor/ServiceLoader700Test.java | 58 ++++++++----------- .../smile/ServiceLoader700Test.java | 48 +++++++-------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java b/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java index 505bb89cb..942332259 100644 --- a/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java +++ b/cbor/src/test/java/tools/jackson/dataformat/cbor/ServiceLoader700Test.java @@ -1,11 +1,9 @@ package tools.jackson.dataformat.cbor; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; +import java.io.File; import java.nio.charset.StandardCharsets; -import java.util.Enumeration; +import java.nio.file.Files; +import java.util.List; import org.junit.jupiter.api.Test; @@ -15,41 +13,35 @@ // [dataformats-binary#700]: SPI file referenced wrong class name // (`...cbor.databind.CBORMapper` instead of `...cbor.CBORMapper`), -// causing `ServiceConfigurationError` from `ServiceLoader`. +// causing `ServiceConfigurationError` from `ServiceLoader` for classpath +// (non-modular) usage. +// +// NOTE: the SPI file only governs classpath usage; modular usage is covered by +// the `provides` directive in `module-info.java`. Since the test harness runs on +// the module path (where the module's own resources are not reachable), this test +// validates the source SPI file content directly instead of via `ServiceLoader`. public class ServiceLoader700Test extends CBORTestBase { - private final static String SERVICE_FILE = - "META-INF/services/tools.jackson.databind.ObjectMapper"; + private final static File SERVICE_FILE = new File( + "src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper"); @Test public void testServiceFileClassNamesResolve() throws Exception { + assertTrue(SERVICE_FILE.exists(), "Missing SPI file: " + SERVICE_FILE.getAbsolutePath()); boolean foundCBORMapper = false; - // Enumerate every copy of the SPI file on the path (cbor, smile, etc.)... - Enumeration resources = getClass().getClassLoader().getResources(SERVICE_FILE); - assertTrue(resources.hasMoreElements(), - "Should find at least one `" + SERVICE_FILE + "` resource"); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - System.err.println("DEBUG url=" + url); - try (InputStream in = url.openStream()) { - BufferedReader r = new BufferedReader( - new InputStreamReader(in, StandardCharsets.UTF_8)); - String line; - while ((line = r.readLine()) != null) { - System.err.println("DEBUG line=" + line); - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) { - continue; - } - // Every class named in any SPI file must resolve to an `ObjectMapper`... - Class cls = Class.forName(line); - assertTrue(ObjectMapper.class.isAssignableFrom(cls), - "Class `" + line + "` (from " + url + ") is not an `ObjectMapper` subtype"); - if (cls == CBORMapper.class) { - foundCBORMapper = true; - } - } + List lines = Files.readAllLines(SERVICE_FILE.toPath(), StandardCharsets.UTF_8); + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Class named in SPI file must actually exist and be an `ObjectMapper`... + Class cls = Class.forName(line); + assertTrue(ObjectMapper.class.isAssignableFrom(cls), + "Class `" + line + "` is not an `ObjectMapper` subtype"); + if (cls == CBORMapper.class) { + foundCBORMapper = true; } } assertTrue(foundCBORMapper, diff --git a/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java b/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java index a2c2e063e..b9c33270f 100644 --- a/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java +++ b/smile/src/test/java/tools/jackson/dataformat/smile/ServiceLoader700Test.java @@ -1,9 +1,9 @@ package tools.jackson.dataformat.smile; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.File; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; import org.junit.jupiter.api.Test; @@ -13,33 +13,35 @@ // [dataformats-binary#700]: SPI file referenced wrong class name // (`...smile.databind.SmileMapper` instead of `...smile.SmileMapper`), -// causing `ServiceConfigurationError` from `ServiceLoader`. +// causing `ServiceConfigurationError` from `ServiceLoader` for classpath +// (non-modular) usage. +// +// NOTE: the SPI file only governs classpath usage; modular usage is covered by +// the `provides` directive in `module-info.java`. Since the test harness runs on +// the module path (where the module's own resources are not reachable), this test +// validates the source SPI file content directly instead of via `ServiceLoader`. public class ServiceLoader700Test extends BaseTestForSmile { - private final static String SERVICE_FILE = - "META-INF/services/tools.jackson.databind.ObjectMapper"; + private final static File SERVICE_FILE = new File( + "src/main/resources/META-INF/services/tools.jackson.databind.ObjectMapper"); @Test public void testServiceFileClassNamesResolve() throws Exception { + assertTrue(SERVICE_FILE.exists(), "Missing SPI file: " + SERVICE_FILE.getAbsolutePath()); boolean foundSmileMapper = false; - try (InputStream in = getClass().getModule().getResourceAsStream(SERVICE_FILE)) { - assertNotNull(in, "Missing SPI resource: " + SERVICE_FILE); - BufferedReader r = new BufferedReader( - new InputStreamReader(in, StandardCharsets.UTF_8)); - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) { - continue; - } - // Class named in SPI file must actually exist and be an `ObjectMapper`... - Class cls = Class.forName(line); - assertTrue(ObjectMapper.class.isAssignableFrom(cls), - "Class `" + line + "` is not an `ObjectMapper` subtype"); - if (cls == SmileMapper.class) { - foundSmileMapper = true; - } + List lines = Files.readAllLines(SERVICE_FILE.toPath(), StandardCharsets.UTF_8); + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Class named in SPI file must actually exist and be an `ObjectMapper`... + Class cls = Class.forName(line); + assertTrue(ObjectMapper.class.isAssignableFrom(cls), + "Class `" + line + "` is not an `ObjectMapper` subtype"); + if (cls == SmileMapper.class) { + foundSmileMapper = true; } } assertTrue(foundSmileMapper,