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/6] 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/6] 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(); } From f7ab71c2060c6fe1ff61676818f2e3f3763aaa48 Mon Sep 17 00:00:00 2001 From: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:03:05 +0530 Subject: [PATCH 3/6] Apply suggestion from @hyperxpro --- .../test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java index d5b5fa6ef3..a578290ed2 100644 --- a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -44,6 +44,7 @@ public void encodeQueryEscapesWhenNeeded() { assertNotSame(query, encoded); assertEquals("a=one%20two", encoded); + assertEquals("a=%C3%A9", Utf8UrlEncoder.encodeQuery("a=\u00e9")); } @RepeatedIfExceptionsTest(repeats = 5) From 1c02e2ff282d257dbd23b200c9a1b31bf0465b07 Mon Sep 17 00:00:00 2001 From: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:09:32 +0530 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com> --- .../main/java/org/asynchttpclient/util/Utf8UrlEncoder.java | 1 + .../java/org/asynchttpclient/util/Utf8UrlEncoderTest.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java index 7cd9d250ca..cd7a7801c8 100644 --- a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java @@ -129,6 +129,7 @@ public static String encodePath(String input) { return sb == null ? input : sb.toString(); } + // package-private: only UriEncoder (same package) needs this; keep API surface minimal static String encodeQuery(String input) { StringBuilder sb = lazyAppendEncoded(null, input, BUILT_QUERY_UNTOUCHED_CHARS, false); return sb == null ? input : sb.toString(); diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java index a578290ed2..ec66f33026 100644 --- a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.util; -import io.github.artsok.RepeatedIfExceptionsTest; +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; @@ -30,14 +30,14 @@ public void testBasics() { assertEquals("a%2Bb", Utf8UrlEncoder.encodeQueryElement("a+b")); } - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void encodeQueryReusesInputWhenNothingNeedsEscaping() { String query = "a=1&b=/two?c%20d"; assertSame(query, Utf8UrlEncoder.encodeQuery(query)); } - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void encodeQueryEscapesWhenNeeded() { String query = "a=one two"; String encoded = Utf8UrlEncoder.encodeQuery(query); From 23192cadba97f642fe4a60984f2caeb517266498 Mon Sep 17 00:00:00 2001 From: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:10:03 +0530 Subject: [PATCH 5/6] Update client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java --- .../test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java index ec66f33026..e24a5515ea 100644 --- a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -47,7 +47,7 @@ public void encodeQueryEscapesWhenNeeded() { assertEquals("a=%C3%A9", Utf8UrlEncoder.encodeQuery("a=\u00e9")); } - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void testPercentageEncoding() { assertEquals("foobar", Utf8UrlEncoder.percentEncodeQueryElement("foobar")); assertEquals("foo%2Abar", Utf8UrlEncoder.percentEncodeQueryElement("foo*bar")); From ce79fcf2f8dd9c3bfe2505ab043c2baf3a677938 Mon Sep 17 00:00:00 2001 From: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:11:37 +0530 Subject: [PATCH 6/6] Update client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java --- .../test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java index e24a5515ea..83268fc13e 100644 --- a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -23,7 +23,7 @@ public class Utf8UrlEncoderTest { - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void testBasics() { assertEquals("foobar", Utf8UrlEncoder.encodeQueryElement("foobar")); assertEquals("a%26b", Utf8UrlEncoder.encodeQueryElement("a&b"));