Check also MIGRATION.md for possible compatibility problems.
Removed method contentTypeOfBody from HttpRequestBuilder.
Added method addHttpClientCustomizer into HttpRequestBuilder
Removed inputStreamDeserializer from ResponseDeserializer. Reason: Response should be deserialized
by ResponseDeserializer
Renamed ignorableDeserializer of ResponseDeserializer to toStringDeserializer
Due to the fact that old implementation was designed only for one predefined URI and wasn't much flexible the 2.x.x
version don't have back compatibility with version 1.x.x. The 1.x.x versions will be supported in branch 1.x.x_support
.
The method getContentAsString of ResponseBodyReaderContext become deprecated and will be removed in further
versions.
The ResponseBodyReaderContext become generic.
The path method of WebTarget now adds path to existed path instead of replacing, for replace use setPath method.
Headers are available from ResponseHandler
-
Starting with 3.3.0 can be used immutable web target.
HttpRequest.immutableTarget(uri) -
Now request methods supported an auto-serializing object to body for content types
application/jsonandapplication/xml -
Added
ResponseHandler.recuiredGet()method.
Added methods ClientBuilder.enableCookieManagement, ClientBuilder.enableAutomaticRetries and
ClientBuilder.setConnectionTimeToLive.
Added methods ClientBuilder.addDefaultConnectionManagerBuilderCustomizer.
- Charset handling was clarified and split:
WebTarget#setCharset(Charset)now sets both the query-string charset and the request body charset.- Added
WebTarget#setQueryCharset(Charset)to control only the URI query-string percent-encoding charset. - Added
WebTarget#setBodyCharset(Charset)to control only the request body charset used by request body converters. - URI path segments are always percent-encoded as UTF-8 per RFC 3986; if you need non-UTF-8 path
encoding, percent-encode the path yourself before passing it to
target(...).
- Default charset for both query-string and body is
UTF-8. HttpRequestBuilder.setDefaultJsonMapper/setDefaultXmlMappernow take a defensive copy of the supplied mapper at the moment the setter is called; the caller's instance is never mutated by the library.addResponseDefaultDateDeserializationPattern/addRequestDefaultDateSerializationPatternnow compose correctly with a user-supplied mapper — previously the patterns were silently dropped.- Retry API refresh (
@Beta— breaking):- New
RetryAttempttype exposesresponse,method,uri,attemptNumber, anderrorto retry predicates. RetryContextmethods now take aRetryAttempt:mustBeRetried(RetryAttempt),getRetryDelay(RetryAttempt)(returnsDuration),beforeRetry(RetryAttempt, WebTarget).- Removed the pre-3.5.0 response-only overloads (
mustBeRetried(Response),getRetryDelay(Response),beforeRetry(WebTarget)). 3.4.x customRetryContextimplementations will no longer compile — seeMIGRATION.mdfor the before/after template. - The default
mustBeRetried(RetryAttempt)is idempotency-gated: only retries GET/HEAD/OPTIONS/PUT/DELETE/TRACE (RFC 9110 §9.2.2) on 503 by default. Retrying POST/PATCH must be an explicit opt-in — see the new factory helperRetryContext.onAnyMethod5xx(int, Duration). - New factory helper
RetryContext.onIdempotent5xx(int, Duration)— safe default that retries idempotent methods on any 5xx, honoringRetry-Afterwhen present. - Added
HttpMethod.isIdempotent()per RFC 9110.
- New
- Retries now work for requests with repeatable bodies. Previously, any retryable request whose
HttpEntityhad been initialized failed on first retry with "After initializing the httpEntity builder can't be copied."HttpUriRequestBuildernow shares the entity reference with the copy whenentity.isRepeatable()istrue(covers all built-in body paths:StringEntity,ByteArrayEntity,FileEntity, and Jackson-produced bodies). Non-repeatable entities (InputStreamEntityand similar streaming sources) still cannot be replayed and now fail with an actionable error pointing at the fix. Response#getContentType()no longer leaksUnsupportedCharsetException(or any otherIllegalArgumentExceptionfromContentType.parse) when a server returns a header likeContent-Type: text/plain; charset=<charset-not-installed-in-this-JVM>. The malformed value is logged at WARN level and the helper returnsnull— the same as "no Content-Type header." Reader chains (isReadable()predicates) now fall through cleanly toResponseBodyReaderNotFoundExceptioninstead of letting an unchecked exception escape from a getter.- Replaced the custom
LimitedInputStreamwithcommons-io'sorg.apache.commons.io.input.BoundedInputStream. The response-body size cap is now enforced by configuringBoundedInputStreamwithsetMaxCount(maxBytes + 1)(so a body of exactlymaxBytesstill drains cleanly to EOF) and ansetOnMaxCountconsumer that throwsInvalidContentLengthExceptionthe moment the cap is exceeded. The swap closes three latent gaps in the previous custom class in one stroke:skip()andread(b, off, len)no longer over-pull the underlying stream past the cap (commons-io clamps both viatoReadLen),markSupported()no longer falsely advertises mark support that would corrupt the byte counter onreset(), and we drop ~70 lines of custom code in favor of a battle-tested upstream implementation.commons-io 2.22.0is now a compile-scope dependency. StringReaderandByteReaderno longer passmaxLentoEntityUtils.toString/EntityUtils.toByteArray. The cap is enforced at the byte level by the wrappedBoundedInputStream; passing amaxLen(which is a character limit fortoString) would silently truncate the result at a char boundary instead of triggering the byte-cap throw.BoundedHttpEntity#writeTois now documented and regression-tested as a load-bearing override for the size-cap guarantee: without itHttpEntityWrapper#writeTowould delegate straight to the wrapped entity, bypassingBoundedInputStreamand silently spooling oversize bodies through callers that useresponse.getEntity().writeTo(...)(e.g. spooling a response to disk). The override now also usesIOUtils.copy(commons-io) instead of a hand-rolled buffered loop.ClientBuilderdefaultdefaultMaxPoolSizePerRoutelowered from128to32.maxPoolSizeis unchanged at128. PreviouslyperRoute == totallet a single hot host saturate the entire pool, leaving parallel requests to other hosts blocked onconnectionRequestTimeout. The newtotal / 4ratio matches industry conventions (Apache HC, AWS SDK, Spring) and preserves multi-host fairness. Single-upstream workloads that want the old behavior should callsetDefaultMaxPoolSizePerRoute(128)explicitly — seeMIGRATION.md.ClientBuilder.proxy(URI)now rejects URIs that include userinfo (e.g.http://user:pass@proxy.corp) withIllegalArgumentExceptionand a message pointing at Apache HC5'sBasicCredentialsProvider. Previously, the userinfo was silently discarded by theHttpHostconstructor — users who thought they were configuring proxy auth would see no credentials sent.ClientBuilder.build()is now idempotent across repeated calls. Previously, thedefaultRequestConfigBuilderanddefaultConnectionConfigBuilderwere stored as fields and user-supplied customizers were applied to those persistent instances on everybuild()— state-dependent customizers (e.g. ones that re-register an interceptor or react to current builder state) would compound across calls. Eachbuild()now snapshots the configured state and applies customizers to a freshBuilderper call.- New
setDefaultResponseCharset(Charset)onHttpRequestBuilder. The library now defaults to UTF-8 for response bodies whoseContent-Typelacks acharsetparameter (Apache HC5's bare default is ISO-8859-1). Server-suppliedcharset=...always wins; the new setting only affects the no-charset path. SeeMIGRATION.mdfor restoring the ISO-8859-1 behavior on legacy servers. - New opt-in SSRF guard
ClientBuilder.disallowPrivateAndLoopbackHosts(). When enabled, the client's DNS resolver rejects any host that resolves to loopback / unspecified / link-local / RFC 1918 / IPv6 unique-local addresses. The check is plumbed through Apache HC5'sDnsResolver, so it fires for every host the client touches — including hosts reached via 3xx redirects, not only the URL the caller passed totarget(...). The same DNS lookup that produces the connection IP is the one being filtered, closing the time-of-check / time-of-use gap a URL-only check would have. Specifically catches user-controlled URLs pointing at cloud-metadata endpoints (e.g.169.254.169.254). Combine with network-layer egress filtering for full defence-in-depth. - New TLS knobs on
ClientBuilder:setTlsVersions(String...)enforces a TLS version allow-list (e.g."TLSv1.3", "TLSv1.2");setCipherSuites(String...)opts out of weak / deprecated ciphers. Both delegate to Apache HC5'sClientTlsStrategyBuilderand use JVM defaults when not called. - New HTTP/1.1 head-size knobs on
ClientBuilder:setMaxHeaderCount(int)caps the per-message header count;setMaxLineLength(int)caps any single line (status line or header). Bounds memory consumption when talking to a hostile / buggy server that emits an unbounded header list — complementssetMaxResponseBodySizeBytes(which only protects the body, not the head). Negative values mean "use Apache HC5's built-in default." - New
HttpRequestBuilder.addPayloadRedactor(Function<String, String>)for masking secrets in request bodies before they are logged viaenableRequestPayloadLogging(). Redactors compose left-to-right; a buggy redactor that throws is caught and swapped for a[redaction-failed]placeholder rather than killing the in-flight request. Redactors are only invoked when payload logging is enabled. - Successful
HEADresponses are no longer remapped to 502. Apache HC5 returnsnullHttpEntityforHEADresponses (HTTP forbids a response body onHEAD), and the previous code blindly treatedhasBody(statusCode) && entity == nullas a server error. The check now excludesHEAD; for non-HEADrequests the existing safety net is kept (Apache HC5 always sets a, possibly length-0, entity forhasBody(...)statuses on those methods, so this branch effectively only fires on genuinely malformed responses). - New
RetryContext.withMaxHonoredRetryAfter(RetryContext, Duration)opt-in clamp onRetry-After. Wraps anyRetryContextand caps the honoured retry delay, defending against a misbehaving or hostile upstream returningRetry-After: 99999(~28 hours) and stalling the calling thread. Default behaviour without the wrapper is unchanged — the library is RFC-compliant and honours whatever the server says. - New
ClientBuilder.disallowPrivateAndLoopbackHosts(Predicate<InetAddress>)overload for the SSRF guard. The predicate is an allow-list escape hatch: it is invoked for each resolved address that would otherwise be blocked, and a return oftruepermits the address through. Useful when you need to block public-internet SSRF surface but still talk to a specific internal endpoint (e.g. an internal config service on10.0.7.42). Passingnullfor the predicate is equivalent to the no-arg overload. - New
HttpRequestBuilder.setDefaultQueryCharset(Charset)andHttpRequestBuilder.setDefaultBodyCharset(Charset). Set the initial value that every {@code WebTarget} produced from the resulting {@code HttpRequest} starts with — applied to targets returned by {@code target(...)}, {@code immutableTarget(...)}, and {@code retryableTarget(...)}. Per-target {@code setQueryCharset} / {@code setBodyCharset} still overrides; passing {@code null} at builder level keeps the library default (UTF-8). Closes the gap where consumers wanting a non-UTF-8 default had to override on every per-call target. {@code setCharset(Charset)} combo on the builder is intentionally not added — explicit per-axis is the only entry point at the builder level, mirroring the pre-existing {@code setDefaultResponseCharset} pattern.
- Upgraded Apache HttpClient 5 (
httpclient5) to5.6.1. Notable upstream behaviour change: the defaultHostnameVerificationPolicyis nowBUILTIN— aHostnameVerifierset viaClientBuilder.hostnameVerifier(...)alone is silently ignored. Pair the call withhostnameVerificationPolicy(HostnameVerificationPolicy.CLIENT)to keep the verifier authoritative (or usetrustAllHosts(), which already wires both). See the Javadoc onClientBuilder.hostnameVerifier(...)for the full note. No other API or behaviour changes.
- Java baseline raised to Java 17. Bytecode target moves from Java 8 to Java 17;
the build enforcer now rejects JDKs below 17. The
3.6.xline remains the supported Java 8 baseline. No public API was renamed, removed, or re-typed — source built against3.6.xrecompiles unchanged on4.0.0. SeeMIGRATION.md. - Published as a JPMS module
com.jsunsoft.http. Addssrc/main/java/module-info.javadeclaring the module withexports com.jsunsoft.httpandexports com.jsunsoft.http.annotations. Apache HttpClient 5 (client + core) and Jacksondatabind/dataformat-xmlarerequires transitive, so modulepath consumers see the public-API types from those modules without re-declaring them. Classpath consumers are unaffected. - Internal modernization to Java 9–17 idioms (no behaviour change): defensive collection
copies now use
List.copyOf/Set.copyOf;HttpMethod.isIdempotent()is a switch expression with compile-time-exhaustive enum cases; pattern-matchinginstanceofapplied inBasicResponseHandler,BasicWebTarget, andTypeReference. - Build:
maven-compiler-pluginconfiguration collapsed to a single<release>17</release>(previously a dual main/test execution); added an explicitmaven-surefire-pluginwith<useModulePath>false</useModulePath>so tests run on the classpath without forcing the production module toopensits package to reflective frameworks.
- Jackson 2.x → 3.x.
jackson-databindandjackson-dataformat-xmlmove fromcom.fasterxml.jackson.{core,dataformat}totools.jackson.{core,dataformat}(3.1.3); JPMS module names follow (tools.jackson.databind,tools.jackson.core,tools.jackson.dataformat.xml).jackson-annotationsstays oncom.fasterxml.jackson.core:jackson-annotations(2.21) — the annotations artifact is intentionally shared between Jackson 2.x and 3.x consumers.jackson-datatype-jdk8,jackson-datatype-jsr310, andjackson-module-parameter-namesare folded intojackson-databindin 3.0 and their standalone declarations are dropped. The Jackson 3 BOM is imported independencyManagementso alltools.jackson.*coordinates resolve to a single coherent release train. See MIGRATION.md for the full upgrade guide — built-in modules, immutable-mapper consequences, exception-hierarchy changes, default-toggle flips, and theHEURISTICsingle-arg constructor binding behaviour. - Mapper handling refactored for Jackson 3's immutable model.
HttpRequestBuilder'ssetDefaultJsonMapper(ObjectMapper)andsetDefaultXmlMapper(ObjectMapper)no longer take a defensive.copy()— Jackson 3 mappers are immutable, so the supplied reference is stored directly; per-config date-pattern overrides applied viaaddResponseDefaultDateDeserializationPattern/addRequestDefaultDateSerializationPatternare installed on a fresh derivative produced viaObjectMapper#rebuild(), leaving the caller's mapper untouched. - Parse-failure routing tightened. Malformed-JSON / type-mismatch responses now surface
as
ResponseBodyReaderException(→ HTTP 502 Bad Gateway on theResponseHandler), consistent withInvalidContentLengthException. In 4.0.0 these incidentally surfaced as 503 Service Unavailable because Jackson 2.x'sJsonProcessingExceptionextendedIOException; Jackson 3 fixed the hierarchy and the library follows the corrected semantics. Stream-level IO failures still route to 503; size-cap failures still route to 502. - Jackson 3 default-toggle changes are accepted as-is rather than papered over with
builderWithJackson2Defaults()— see MIGRATION.md for the per-toggle table (most are strict/safe wins; the visible deltas areREAD/WRITE_ENUMS_USING_TO_STRINGandSORT_PROPERTIES_ALPHABETICALLY).