From e9b307675235cd418d504e1ddb20803aa0ec98df Mon Sep 17 00:00:00 2001 From: Pavel Ptashyts <49400901+pavel-ptashyts@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:48:43 +0200 Subject: [PATCH 1/2] Avoid rebuilding clean query strings Add a lazy query-string encoder that mirrors the existing path encoder fast path. When a query contains only pass-through characters, UriEncoder now reuses the original String instead of allocating a pooled StringBuilder and an equal replacement String. The append-based path is unchanged for URLs with additional query params. Existing escaping behavior is preserved for queries that need percent-encoding. Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex --- .../org/asynchttpclient/util/UriEncoder.java | 4 +--- .../asynchttpclient/util/Utf8UrlEncoder.java | 5 +++++ .../util/Utf8UrlEncoderTest.java | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java index d205aed6ff..d94d0770b4 100644 --- a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java @@ -62,9 +62,7 @@ protected String withQueryWithParams(final String query, final List query @Override protected String withQueryWithoutParams(final String query) { // encode query - StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); - encodeAndAppendQuery(sb, query); - return sb.toString(); + return Utf8UrlEncoder.encodeQuery(query); } @Override diff --git a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java index fe01e32087..ef84c74cee 100644 --- a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java @@ -129,6 +129,11 @@ public static String encodePath(String input) { return sb == null ? input : sb.toString(); } + public static String encodeQuery(String input) { + StringBuilder sb = lazyAppendEncoded(null, input, BUILT_QUERY_UNTOUCHED_CHARS, false); + return sb == null ? input : sb.toString(); + } + public static StringBuilder encodeAndAppendQuery(StringBuilder sb, String query) { return appendEncoded(sb, query, BUILT_QUERY_UNTOUCHED_CHARS, false); } diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java index ee3966cf0f..d5b5fa6ef3 100644 --- a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -18,6 +18,8 @@ import io.github.artsok.RepeatedIfExceptionsTest; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; public class Utf8UrlEncoderTest { @@ -28,6 +30,22 @@ public void testBasics() { assertEquals("a%2Bb", Utf8UrlEncoder.encodeQueryElement("a+b")); } + @RepeatedIfExceptionsTest(repeats = 5) + public void encodeQueryReusesInputWhenNothingNeedsEscaping() { + String query = "a=1&b=/two?c%20d"; + + assertSame(query, Utf8UrlEncoder.encodeQuery(query)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void encodeQueryEscapesWhenNeeded() { + String query = "a=one two"; + String encoded = Utf8UrlEncoder.encodeQuery(query); + + assertNotSame(query, encoded); + assertEquals("a=one%20two", encoded); + } + @RepeatedIfExceptionsTest(repeats = 5) public void testPercentageEncoding() { assertEquals("foobar", Utf8UrlEncoder.percentEncodeQueryElement("foobar")); From b4badf5ae834e7df0c2587d0db5e130f3de46a0b Mon Sep 17 00:00:00 2001 From: Pavel Ptashyts <49400901+pavel-ptashyts@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:27:58 +0200 Subject: [PATCH 2/2] Keep query helper package-private The lazy query encoder is an implementation detail used only by UriEncoder and same-package tests. Publishing it would expand the supported API without a caller-facing requirement. Keep the allocation optimization while limiting the helper to its package. Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex --- .../src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java index ef84c74cee..7cd9d250ca 100644 --- a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java @@ -129,7 +129,7 @@ public static String encodePath(String input) { return sb == null ? input : sb.toString(); } - public static String encodeQuery(String input) { + static String encodeQuery(String input) { StringBuilder sb = lazyAppendEncoded(null, input, BUILT_QUERY_UNTOUCHED_CHARS, false); return sb == null ? input : sb.toString(); }