diff --git a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
index 92706d292..d205aed6f 100644
--- a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
+++ b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
@@ -20,6 +20,7 @@
import org.jetbrains.annotations.Nullable;
import java.util.List;
+import java.util.Objects;
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
import static org.asynchttpclient.util.Utf8UrlEncoder.encodeAndAppendQuery;
@@ -143,6 +144,13 @@ private String withQuery(final String query, final @Nullable List queryPa
public Uri encode(Uri uri, @Nullable List queryParams) {
String newPath = encodePath(uri.getPath());
String newQuery = encodeQuery(uri.getQuery(), queryParams);
+ // Common case (already-encoded URL, no extra query params): encoding changes nothing, so reuse the
+ // input Uri instead of allocating an identical copy. encodePath returns the same String instance
+ // when nothing needs escaping; newQuery may be a freshly built but equal String, so compare by
+ // value. Every other Uri field is copied unchanged, so equal path+query means an identical Uri.
+ if (newPath.equals(uri.getPath()) && Objects.equals(newQuery, uri.getQuery())) {
+ return uri;
+ }
return new Uri(uri.getScheme(),
uri.getUserInfo(),
uri.getHost(),
diff --git a/client/src/test/java/org/asynchttpclient/util/UriEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/UriEncoderTest.java
new file mode 100644
index 000000000..389579665
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/util/UriEncoderTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2026 AsyncHttpClient Project. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.asynchttpclient.util;
+
+import org.asynchttpclient.Param;
+import org.asynchttpclient.uri.Uri;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Tests {@link UriEncoder#encode(Uri, List)}'s fast path: when an already-encoded URL is encoded with no
+ * extra query params, encoding changes nothing, so the SAME Uri instance is returned (no wasted copy).
+ * When anything actually changes (added params, or a path/query needing escaping), a new equal-or-encoded
+ * Uri is returned.
+ */
+public class UriEncoderTest {
+
+ private static final UriEncoder FIXING = UriEncoder.uriEncoder(false);
+ private static final UriEncoder RAW = UriEncoder.uriEncoder(true);
+
+ @Test
+ public void returnsSameInstanceWhenNothingChanges() {
+ Uri uri = Uri.create("http://www.example.com/path/to/resource?a=1&b=2");
+ assertSame(uri, FIXING.encode(uri, null), "clean URL + no params should reuse the input Uri");
+ assertSame(uri, FIXING.encode(uri, Collections.emptyList()), "empty param list should also reuse it");
+ assertSame(uri, RAW.encode(uri, null), "RAW on a clean URL should reuse the input Uri");
+ }
+
+ @Test
+ public void returnsSameInstanceWhenNoQueryAndNoParams() {
+ Uri uri = Uri.create("http://www.example.com/path");
+ assertSame(uri, FIXING.encode(uri, null));
+ }
+
+ @Test
+ public void rebuildsWhenQueryParamsAdded() {
+ Uri uri = Uri.create("http://www.example.com/path?a=1");
+ List params = Collections.singletonList(new Param("b", "2"));
+ Uri encoded = FIXING.encode(uri, params);
+ assertNotSame(uri, encoded, "adding a query param must produce a new Uri");
+ assertEquals("a=1&b=2", encoded.getQuery());
+ // Untouched fields are preserved.
+ assertEquals(uri.getScheme(), encoded.getScheme());
+ assertEquals(uri.getHost(), encoded.getHost());
+ assertEquals(uri.getPath(), encoded.getPath());
+ }
+
+ @Test
+ public void rebuildsWhenPathNeedsEncoding() {
+ // A space in the path must be percent-encoded by FIXING, so the result differs from the input.
+ Uri uri = Uri.create("http://www.example.com/a b");
+ Uri encoded = FIXING.encode(uri, null);
+ assertNotSame(uri, encoded, "a path needing escaping must produce a new Uri");
+ assertEquals("/a%20b", encoded.getPath());
+ }
+}