From 4397c9f5616a7e6fd3f1d0416e5ba925030a0df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 22 May 2026 11:13:04 +0200 Subject: [PATCH 1/5] task: Use getInputStream to get data As reported in #377 - getContent() when building with GraalVM uses serviceloaders to locate the correct content processor. But we've already checked that we don't have a gzip stream, so we're trusting our upstream to return application/json and do not need a content processor. This saves us a cast from content to InputStream as well. fixes: #377 --- pom.xml | 63 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index 20fa19b23..713f877a1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,13 @@ - - + + 4.0.0 io.getunleash unleash-client-java - 12.2.3-SNAPSHOT + 12.2.2-SNAPSHOT UTF-8 @@ -53,8 +57,10 @@ https://github.com/Unleash/unleash-java-sdk - scm:git:https://github.com/Unleash/unleash-java-sdk.git - scm:git:https://github.com/Unleash/unleash-java-sdk.git + scm:git:https://github.com/Unleash/unleash-java-sdk.git + scm:git:https://github.com/Unleash/unleash-java-sdk.git @@ -188,7 +194,8 @@ 3.5.2 1 - @{argLine} -javaagent:${org.mockito:mockito-core:jar} + @{argLine} -javaagent:${org.mockito:mockito-core:jar} @@ -198,8 +205,10 @@ - true - true + true + true @@ -235,9 +244,11 @@ https://github.com - Unleash/client-specification/archive/v${version.unleash.specification}.zip + Unleash/client-specification/archive/v${version.unleash.specification}.zip - ${project.build.directory}/client-specification + ${project.build.directory}/client-specification @@ -261,7 +272,8 @@ ${project.build.directory}/client-specification/v${version.unleash.specification}.zip - ${project.build.directory}/client-specification/ + ${project.build.directory}/client-specification/ @@ -281,7 +293,8 @@ copy-resources - ${project.build.testOutputDirectory}/client-specification + ${project.build.testOutputDirectory}/client-specification @@ -325,8 +338,10 @@ ALWAYS - https://central.sonatype.com/api/v1/publisher - target/staging-deploy + https://central.sonatype.com/api/v1/publisher + target/staging-deploy 60 40 @@ -402,7 +417,8 @@ maven-surefire-plugin 3.5.2 - @{argLine} -javaagent:${org.mockito:mockito-core:jar} + @{argLine} -javaagent:${org.mockito:mockito-core:jar} @@ -414,13 +430,15 @@ false - https://s01.oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots + https://oss.sonatype.org/content/repositories/snapshots false @@ -438,7 +456,8 @@ sonatype-nexus-staging Nexus Release Repository - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ @@ -507,7 +526,8 @@ publication - local::file:./target/staging-deploy + local::file:./target/staging-deploy deploy @@ -544,7 +564,7 @@ - + sonatype-nexus-snapshots Sonatype Nexus Snapshots @@ -553,7 +573,8 @@ sonatype-nexus-staging Nexus Release Repository - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ From 89aca5f15fdfc7520ea7e7ce5aac9ba4c9feb1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 22 May 2026 11:09:49 +0200 Subject: [PATCH 2/5] chore(test): graalvm test --- .github/workflows/pull_requests.yml | 22 ++++++ .../GraalVmHttpFeatureFetcherSmoke.java | 72 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index ac9c95f49..0203ef7b1 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -29,6 +29,28 @@ jobs: flag-name: run-jvm-${{ join(matrix.*, '-') }} parallel: true base-path: src/main/java + + graalvm-native-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup GraalVM + uses: graalvm/setup-graalvm@v1 + with: + distribution: graalvm + java-version: "21" + cache: maven + - name: Compile smoke test + run: ./mvnw -B -DskipTests test-compile dependency:build-classpath -Dmdep.outputFile=target/test-classpath.txt + - name: Build native image + run: | + native-image --no-fallback --enable-url-protocols=http \ + -cp "target/classes:target/test-classes:$(cat target/test-classpath.txt)" \ + io.getunleash.repository.GraalVmHttpFeatureFetcherSmoke \ + target/graalvm-http-feature-fetcher-smoke + - name: Run native image smoke test + run: ./target/graalvm-http-feature-fetcher-smoke finish: needs: build if: ${{ always() }} diff --git a/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java b/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java new file mode 100644 index 000000000..6b2f7ee36 --- /dev/null +++ b/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java @@ -0,0 +1,72 @@ +package io.getunleash.repository; + +import io.getunleash.event.ClientFeaturesResponse; +import io.getunleash.util.UnleashConfig; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +public class GraalVmHttpFeatureFetcherSmoke { + private static final String FEATURE_NAME = "graalvm-smoke-test"; + private static final String RESPONSE_BODY = + "{\"version\":1,\"features\":[{\"name\":\"" + + FEATURE_NAME + + "\",\"enabled\":true,\"strategies\":[{\"name\":\"default\"}]}]}"; + + public static void main(String[] args) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + Thread server = new Thread(() -> serveOneRequest(serverSocket)); + server.start(); + + UnleashConfig config = + UnleashConfig.builder() + .appName("graalvm-native-image-smoke-test") + .unleashAPI( + new URI( + "http://127.0.0.1:" + + serverSocket.getLocalPort() + + "/api/")) + .build(); + + ClientFeaturesResponse response = new HttpFeatureFetcher(config).fetchFeatures(); + boolean found = + response.getFeatures().stream() + .anyMatch(feature -> FEATURE_NAME.equals(feature.getName())); + if (response.getStatus() != ClientFeaturesResponse.Status.CHANGED || !found) { + throw new IllegalStateException("Native image smoke test did not fetch features"); + } + + server.join(); + } + } + + private static void serveOneRequest(ServerSocket serverSocket) { + try (Socket socket = serverSocket.accept(); + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + socket.getInputStream(), StandardCharsets.US_ASCII)); + OutputStream output = socket.getOutputStream()) { + String line; + while ((line = reader.readLine()) != null && !line.isEmpty()) {} + + byte[] body = RESPONSE_BODY.getBytes(StandardCharsets.UTF_8); + output.write( + ("HTTP/1.1 200 OK\r\n" + + "Content-Type: application/json\r\n" + + "Content-Length: " + + body.length + + "\r\n" + + "Connection: close\r\n\r\n") + .getBytes(StandardCharsets.US_ASCII)); + output.write(body); + output.flush(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} From 94bab2a177aab564de68852d7ac03f0e96a549e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 22 May 2026 11:17:14 +0200 Subject: [PATCH 3/5] Restore pom xml from main --- pom.xml | 63 +++++++++++++++++++-------------------------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/pom.xml b/pom.xml index 713f877a1..20fa19b23 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,9 @@ - - + + 4.0.0 io.getunleash unleash-client-java - 12.2.2-SNAPSHOT + 12.2.3-SNAPSHOT UTF-8 @@ -57,10 +53,8 @@ https://github.com/Unleash/unleash-java-sdk - scm:git:https://github.com/Unleash/unleash-java-sdk.git - scm:git:https://github.com/Unleash/unleash-java-sdk.git + scm:git:https://github.com/Unleash/unleash-java-sdk.git + scm:git:https://github.com/Unleash/unleash-java-sdk.git @@ -194,8 +188,7 @@ 3.5.2 1 - @{argLine} -javaagent:${org.mockito:mockito-core:jar} + @{argLine} -javaagent:${org.mockito:mockito-core:jar} @@ -205,10 +198,8 @@ - true - true + true + true @@ -244,11 +235,9 @@ https://github.com - Unleash/client-specification/archive/v${version.unleash.specification}.zip + Unleash/client-specification/archive/v${version.unleash.specification}.zip - ${project.build.directory}/client-specification + ${project.build.directory}/client-specification @@ -272,8 +261,7 @@ ${project.build.directory}/client-specification/v${version.unleash.specification}.zip - ${project.build.directory}/client-specification/ + ${project.build.directory}/client-specification/ @@ -293,8 +281,7 @@ copy-resources - ${project.build.testOutputDirectory}/client-specification + ${project.build.testOutputDirectory}/client-specification @@ -338,10 +325,8 @@ ALWAYS - https://central.sonatype.com/api/v1/publisher - target/staging-deploy + https://central.sonatype.com/api/v1/publisher + target/staging-deploy 60 40 @@ -417,8 +402,7 @@ maven-surefire-plugin 3.5.2 - @{argLine} -javaagent:${org.mockito:mockito-core:jar} + @{argLine} -javaagent:${org.mockito:mockito-core:jar} @@ -430,15 +414,13 @@ false - https://s01.oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots + https://oss.sonatype.org/content/repositories/snapshots false @@ -456,8 +438,7 @@ sonatype-nexus-staging Nexus Release Repository - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ @@ -526,8 +507,7 @@ publication - local::file:./target/staging-deploy + local::file:./target/staging-deploy deploy @@ -564,7 +544,7 @@ - + sonatype-nexus-snapshots Sonatype Nexus Snapshots @@ -573,8 +553,7 @@ sonatype-nexus-staging Nexus Release Repository - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ From 2c66ded006f8b488e358fd2a3c5d4785123d41fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 22 May 2026 11:22:06 +0200 Subject: [PATCH 4/5] Undo fix from main to validate if graalvm fails --- src/main/java/io/getunleash/repository/HttpFeatureFetcher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java b/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java index 0e389f2c7..d74d1f5b8 100644 --- a/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java +++ b/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java @@ -75,7 +75,8 @@ private ClientFeaturesResponse getFeatureResponse( try (BufferedReader reader = new BufferedReader( new InputStreamReader( - request.getInputStream(), StandardCharsets.UTF_8))) { + (InputStream) request.getContent(), + StandardCharsets.UTF_8))) { String clientFeatures = reader.lines().collect(Collectors.joining("\n")); return ClientFeaturesResponse.updated(clientFeatures); From 6cec1ab84c920ced7373c03d80c56b9f2b124726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 22 May 2026 11:23:12 +0200 Subject: [PATCH 5/5] Solve PR comments --- .github/workflows/pull_requests.yml | 3 ++- .../GraalVmHttpFeatureFetcherSmoke.java | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 0203ef7b1..f63107d8f 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -40,6 +40,7 @@ jobs: with: distribution: graalvm java-version: "21" + components: native-image cache: maven - name: Compile smoke test run: ./mvnw -B -DskipTests test-compile dependency:build-classpath -Dmdep.outputFile=target/test-classpath.txt @@ -52,7 +53,7 @@ jobs: - name: Run native image smoke test run: ./target/graalvm-http-feature-fetcher-smoke finish: - needs: build + needs: [build, graalvm-native-image] if: ${{ always() }} runs-on: ubuntu-latest steps: diff --git a/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java b/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java index 6b2f7ee36..fb290e461 100644 --- a/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java +++ b/src/test/java/io/getunleash/repository/GraalVmHttpFeatureFetcherSmoke.java @@ -9,6 +9,7 @@ import java.net.Socket; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.concurrent.FutureTask; public class GraalVmHttpFeatureFetcherSmoke { private static final String FEATURE_NAME = "graalvm-smoke-test"; @@ -19,8 +20,13 @@ public class GraalVmHttpFeatureFetcherSmoke { public static void main(String[] args) throws Exception { try (ServerSocket serverSocket = new ServerSocket(0)) { - Thread server = new Thread(() -> serveOneRequest(serverSocket)); - server.start(); + FutureTask server = + new FutureTask<>( + () -> { + serveOneRequest(serverSocket); + return null; + }); + new Thread(server, "graalvm-smoke-http").start(); UnleashConfig config = UnleashConfig.builder() @@ -40,11 +46,11 @@ public static void main(String[] args) throws Exception { throw new IllegalStateException("Native image smoke test did not fetch features"); } - server.join(); + server.get(); } } - private static void serveOneRequest(ServerSocket serverSocket) { + private static void serveOneRequest(ServerSocket serverSocket) throws Exception { try (Socket socket = serverSocket.accept(); BufferedReader reader = new BufferedReader( @@ -52,7 +58,9 @@ private static void serveOneRequest(ServerSocket serverSocket) { socket.getInputStream(), StandardCharsets.US_ASCII)); OutputStream output = socket.getOutputStream()) { String line; - while ((line = reader.readLine()) != null && !line.isEmpty()) {} + while ((line = reader.readLine()) != null && !line.isEmpty()) { + // Drain request headers before writing the response. + } byte[] body = RESPONSE_BODY.getBytes(StandardCharsets.UTF_8); output.write( @@ -65,8 +73,6 @@ private static void serveOneRequest(ServerSocket serverSocket) { .getBytes(StandardCharsets.US_ASCII)); output.write(body); output.flush(); - } catch (Exception e) { - throw new RuntimeException(e); } } }