From 1e4794bd7ba3575e3f1fcb9d3a4a511ce683f012 Mon Sep 17 00:00:00 2001 From: seonwooj0810 Date: Wed, 27 May 2026 09:42:04 +0900 Subject: [PATCH] [Java][restclient] Build XmlMapper via builder when useJackson3=true Jackson 3 made ObjectMapper immutable, so the XmlMapper.configure(...) and XmlMapper.registerModule(...) instance methods used by the restclient ApiClient.mustache template were removed. Generated clients built with `library=restclient`, `withXml=true`, `useJackson3=true` therefore fail to compile. Switch the Jackson 3 branch of the template to the XmlMapper.Builder API (XmlMapper.builder().enable(XmlWriteFeature.WRITE_XML_DECLARATION) .addModule(...).build()), mirroring the JsonMapper.builder() pattern already used a few lines above for the JSON mapper. The Jackson 2 path is unchanged. Adds two regression tests in JavaClientCodegenTest that pin down the expected Jackson 3 XmlMapper construction, with and without openApiNullable. fix #23860 --- .../libraries/restclient/ApiClient.mustache | 20 ++++-- .../codegen/java/JavaClientCodegenTest.java | 71 +++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/restclient/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/restclient/ApiClient.mustache index 61dac36a2540..a35a6c4dc71b 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/restclient/ApiClient.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/restclient/ApiClient.mustache @@ -4,7 +4,12 @@ package {{invokerPackage}}; {{#withXml}} import {{jacksonPackage}}.dataformat.xml.XmlMapper; +{{^useJackson3}} import {{jacksonPackage}}.dataformat.xml.ser.ToXmlGenerator; +{{/useJackson3}} +{{#useJackson3}} +import {{jacksonPackage}}.dataformat.xml.XmlWriteFeature; +{{/useJackson3}} {{/withXml}} import {{jacksonPackage}}.databind.DeserializationFeature; @@ -238,16 +243,21 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { */ public static RestClient.Builder buildRestClientBuilder({{#useJackson3}}JsonMapper{{/useJackson3}}{{^useJackson3}}ObjectMapper{{/useJackson3}} mapper) { {{#withXml}} + {{#useJackson3}} + XmlMapper xmlMapper = XmlMapper.builder() + .enable(XmlWriteFeature.WRITE_XML_DECLARATION) + {{#openApiNullable}} + .addModule(new JsonNullableJackson3Module()) + {{/openApiNullable}} + .build(); + {{/useJackson3}} + {{^useJackson3}} XmlMapper xmlMapper = new XmlMapper(); xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); {{#openApiNullable}} - {{^useJackson3}} xmlMapper.registerModule(new JsonNullableModule()); - {{/useJackson3}} - {{#useJackson3}} - xmlMapper.registerModule(new JsonNullableJackson3Module()); - {{/useJackson3}} {{/openApiNullable}} + {{/useJackson3}} {{/withXml}} {{#useJackson3}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index e593b0cccf5c..2fc1135296a5 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -3453,6 +3453,77 @@ public void testRestClientWithXML_issue_19137() { ); } + @Test(description = "Regression test for issue #23860: with useJackson3=true the generated" + + " restclient ApiClient must build XmlMapper via the immutable Builder API," + + " because XmlMapper.configure(...) and registerModule(...) were removed in Jackson 3.") + public void testRestClientWithXMLAndJackson3_issue_23860() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(JAVA_GENERATOR) + .setLibrary(JavaClientCodegen.RESTCLIENT) + .setAdditionalProperties(Map.of( + CodegenConstants.API_PACKAGE, "xyz.abcdef.api", + CodegenConstants.WITH_XML, true, + JavaClientCodegen.USE_JACKSON_3, true, + JavaClientCodegen.USE_SPRING_BOOT4, true, + JavaClientCodegen.OPENAPI_NULLABLE, false + )) + .setInputSpec("src/test/resources/3_1/java/petstore.yaml") + .setOutputDir(output.toString().replace("\\", "/")); + + List files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + validateJavaSourceFiles(files); + assertFileContains( + output.resolve("src/main/java/xyz/abcdef/ApiClient.java"), + "import tools.jackson.dataformat.xml.XmlMapper;", + "import tools.jackson.dataformat.xml.XmlWriteFeature;", + "XmlMapper xmlMapper = XmlMapper.builder()", + ".enable(XmlWriteFeature.WRITE_XML_DECLARATION)", + ".build();" + ); + TestUtils.assertFileNotContains( + output.resolve("src/main/java/xyz/abcdef/ApiClient.java"), + "xmlMapper.configure(", + "xmlMapper.registerModule(", + "import tools.jackson.dataformat.xml.ser.ToXmlGenerator;" + ); + } + + @Test(description = "Regression test for issue #23860: with useJackson3=true and" + + " openApiNullable=true, the JsonNullableJackson3Module must be wired via the" + + " XmlMapper.Builder#addModule API.") + public void testRestClientWithXMLAndJackson3AndOpenApiNullable_issue_23860() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(JAVA_GENERATOR) + .setLibrary(JavaClientCodegen.RESTCLIENT) + .setAdditionalProperties(Map.of( + CodegenConstants.API_PACKAGE, "xyz.abcdef.api", + CodegenConstants.WITH_XML, true, + JavaClientCodegen.USE_JACKSON_3, true, + JavaClientCodegen.USE_SPRING_BOOT4, true, + JavaClientCodegen.OPENAPI_NULLABLE, true + )) + .setInputSpec("src/test/resources/3_1/java/petstore.yaml") + .setOutputDir(output.toString().replace("\\", "/")); + + List files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + validateJavaSourceFiles(files); + assertFileContains( + output.resolve("src/main/java/xyz/abcdef/ApiClient.java"), + "import org.openapitools.jackson.nullable.JsonNullableJackson3Module;", + "XmlMapper xmlMapper = XmlMapper.builder()", + ".addModule(new JsonNullableJackson3Module())", + ".build();" + ); + TestUtils.assertFileNotContains( + output.resolve("src/main/java/xyz/abcdef/ApiClient.java"), + "xmlMapper.registerModule(" + ); + } + @Test public void testRestClientWithUseSingleRequestParameter_issue_19406() {