@@ -87,9 +87,36 @@ public static URIBuilder loopbackAddress() {
8787 private Charset charset ;
8888 private String fragment ;
8989 private String encodedFragment ;
90+ private EncodingPolicy encodingPolicy = EncodingPolicy .ALL_RESERVED ;
9091
9192 private boolean plusAsBlank ;
9293
94+ /**
95+ * Defines the encoding policy for URI components in {@link URIBuilder}.
96+ * This enum controls how characters are percent-encoded when constructing a URI,
97+ * allowing flexibility between strict encoding and RFC 3986-compliant behavior.
98+ *
99+ * @since 5.4
100+ */
101+ public enum EncodingPolicy {
102+ /**
103+ * Encodes all reserved characters, allowing only unreserved characters
104+ * (ALPHA, DIGIT, "-", ".", "_", "~") to remain unencoded. This is a strict
105+ * policy suitable for conservative URI production where maximum encoding
106+ * is desired.
107+ */
108+ ALL_RESERVED ,
109+
110+ /**
111+ * Follows RFC 3986 component-specific encoding rules. For example, query and
112+ * fragment components allow unreserved characters, sub-delimiters ("!", "$",
113+ * "&", "'", "(", ")", "*", "+", ",", ";", "="), and additional characters
114+ * (":", "@", "/", "?") to remain unencoded, as defined by {@code PercentCodec.FRAGMENT}.
115+ * This policy ensures compliance with RFC 3986 while maintaining interoperability.
116+ */
117+ RFC_3986
118+ }
119+
93120 /**
94121 * Constructs an empty instance.
95122 */
@@ -175,6 +202,22 @@ public URIBuilder setCharset(final Charset charset) {
175202 return this ;
176203 }
177204
205+ /**
206+ * Sets the encoding policy for this {@link URIBuilder}.
207+ * The encoding policy determines how URI components (e.g., query, fragment) are
208+ * percent-encoded when building the URI string. If not set, the default policy
209+ * is {@link EncodingPolicy#RFC_3986}.
210+ *
211+ * @param encodingPolicy the encoding policy to apply, or {@code null} to reset
212+ * to the default ({@link EncodingPolicy#RFC_3986})
213+ * @return this {@link URIBuilder} instance for method chaining
214+ * @since 5.4
215+ */
216+ public URIBuilder setEncodingPolicy (final EncodingPolicy encodingPolicy ) {
217+ this .encodingPolicy = encodingPolicy ;
218+ return this ;
219+ }
220+
178221 /**
179222 * Gets the authority.
180223 *
@@ -356,18 +399,22 @@ private String buildString() {
356399 } else if (this .userInfo != null ) {
357400 final int idx = this .userInfo .indexOf (':' );
358401 if (idx != -1 ) {
359- PercentCodec .encode (sb , this .userInfo .substring (0 , idx ), this .charset , PercentCodec .USERINFO , false );
402+ PercentCodec .encode (sb , this .userInfo .substring (0 , idx ), this .charset ,
403+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .UNRESERVED : PercentCodec .USERINFO , false );
360404 sb .append (':' );
361- PercentCodec .encode (sb , this .userInfo .substring (idx + 1 ), this .charset , PercentCodec .USERINFO , false );
405+ PercentCodec .encode (sb , this .userInfo .substring (idx + 1 ), this .charset ,
406+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .UNRESERVED : PercentCodec .USERINFO , false );
362407 } else {
363- PercentCodec .encode (sb , this .userInfo , this .charset , PercentCodec .USERINFO , false );
408+ PercentCodec .encode (sb , this .userInfo , this .charset ,
409+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .UNRESERVED : PercentCodec .USERINFO , false );
364410 }
365411 sb .append ("@" );
366412 }
367413 if (InetAddressUtils .isIPv6 (this .host )) {
368414 sb .append ("[" ).append (this .host ).append ("]" );
369415 } else {
370- PercentCodec .encode (sb , this .host , this .charset , PercentCodec .REG_NAME , false );
416+ PercentCodec .encode (sb , this .host , this .charset ,
417+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .UNRESERVED : PercentCodec .REG_NAME , false );
371418 }
372419 if (this .port >= 0 ) {
373420 sb .append (":" ).append (this .port );
@@ -391,14 +438,16 @@ private String buildString() {
391438 formatQuery (sb , this .queryParams , this .charset , false );
392439 } else if (this .query != null ) {
393440 sb .append ("?" );
394- PercentCodec .encode (sb , this .query , this .charset , PercentCodec .QUERY , false );
441+ PercentCodec .encode (sb , this .query , this .charset ,
442+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .URIC : PercentCodec .QUERY , false );
395443 }
396444 }
397445 if (this .encodedFragment != null ) {
398446 sb .append ("#" ).append (this .encodedFragment );
399447 } else if (this .fragment != null ) {
400448 sb .append ("#" );
401- PercentCodec .encode (sb , this .fragment , this .charset , PercentCodec .FRAGMENT , false );
449+ PercentCodec .encode (sb , this .fragment , this .charset ,
450+ encodingPolicy == EncodingPolicy .ALL_RESERVED ? PercentCodec .UNRESERVED : PercentCodec .FRAGMENT , false );
402451 }
403452 return sb .toString ();
404453 }
0 commit comments