Skip to content

Commit e075cc5

Browse files
horghclaude
andcommitted
Add httpClient() method to WebServiceClient.Builder
This allows users to provide a custom HttpClient for full control over client configuration. When a custom HttpClient is provided, the builder validates that conflicting parameters (connectTimeout, proxy) are not also set, ensuring clear configuration ownership. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 4f6b400 commit e075cc5

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
------------------
6+
7+
* `WebServiceClient.Builder` now has an `httpClient()` method to allow
8+
passing in a custom `HttpClient`.
9+
410
4.3.1 (2025-05-28)
511
------------------
612

src/main/java/com/maxmind/geoip2/WebServiceClient.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,16 @@ private WebServiceClient(Builder builder) {
140140
.build();
141141

142142
requestTimeout = builder.requestTimeout;
143-
httpClient = HttpClient.newBuilder()
144-
.connectTimeout(builder.connectTimeout)
145-
.proxy(builder.proxy)
146-
.build();
143+
144+
// Use custom HttpClient if provided, otherwise create a default one
145+
if (builder.httpClient != null) {
146+
httpClient = builder.httpClient;
147+
} else {
148+
httpClient = HttpClient.newBuilder()
149+
.connectTimeout(builder.connectTimeout)
150+
.proxy(builder.proxy)
151+
.build();
152+
}
147153
}
148154

149155
/**
@@ -176,6 +182,7 @@ public static final class Builder {
176182

177183
List<String> locales = Collections.singletonList("en");
178184
private ProxySelector proxy = ProxySelector.getDefault();
185+
private HttpClient httpClient = null;
179186

180187
/**
181188
* @param accountId Your MaxMind account ID.
@@ -296,11 +303,38 @@ public Builder proxy(ProxySelector val) {
296303
return this;
297304
}
298305

306+
/**
307+
* @param val the custom HttpClient to use for requests. When providing a
308+
* custom HttpClient, you cannot also set connectTimeout or proxy
309+
* parameters as these should be configured on the provided client.
310+
* @return Builder object
311+
*/
312+
public Builder httpClient(HttpClient val) {
313+
this.httpClient = val;
314+
return this;
315+
}
316+
299317
/**
300318
* @return an instance of {@code WebServiceClient} created from the
301319
* fields set on this builder.
320+
* @throws IllegalArgumentException if httpClient is provided along with
321+
* connectTimeout or proxy settings
302322
*/
303323
public WebServiceClient build() {
324+
if (httpClient != null) {
325+
// Check if connectTimeout was changed from default
326+
if (!connectTimeout.equals(Duration.ofSeconds(3))) {
327+
throw new IllegalArgumentException(
328+
"Cannot set both httpClient and connectTimeout. "
329+
+ "Configure timeout on the provided HttpClient instead.");
330+
}
331+
// Check if proxy was changed from default
332+
if (proxy != ProxySelector.getDefault()) {
333+
throw new IllegalArgumentException(
334+
"Cannot set both httpClient and proxy. "
335+
+ "Configure proxy on the provided HttpClient instead.");
336+
}
337+
}
304338
return new WebServiceClient(this);
305339
}
306340
}

src/test/java/com/maxmind/geoip2/WebServiceClientTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@
3636
import com.maxmind.geoip2.record.Traits;
3737
import java.io.UnsupportedEncodingException;
3838
import java.net.InetAddress;
39+
import java.net.InetSocketAddress;
40+
import java.net.ProxySelector;
41+
import java.net.http.HttpClient;
3942
import java.nio.charset.StandardCharsets;
43+
import java.time.Duration;
4044
import java.util.List;
4145
import org.hamcrest.CoreMatchers;
4246
import org.junit.jupiter.api.Test;
@@ -407,4 +411,53 @@ private WebServiceClient createClient(String service, String ip, int status, Str
407411
.disableHttps()
408412
.build();
409413
}
414+
415+
@Test
416+
public void testHttpClientWithConnectTimeoutThrowsException() {
417+
HttpClient customClient = HttpClient.newBuilder()
418+
.connectTimeout(Duration.ofSeconds(10))
419+
.build();
420+
421+
WebServiceClient.Builder builder = new WebServiceClient.Builder(6, "0123456789")
422+
.httpClient(customClient)
423+
.connectTimeout(Duration.ofSeconds(5));
424+
425+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, builder::build);
426+
assertEquals("Cannot set both httpClient and connectTimeout. Configure timeout on the provided HttpClient instead.",
427+
ex.getMessage());
428+
}
429+
430+
@Test
431+
public void testHttpClientWithProxyThrowsException() {
432+
HttpClient customClient = HttpClient.newBuilder()
433+
.connectTimeout(Duration.ofSeconds(10))
434+
.build();
435+
436+
ProxySelector proxySelector = ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080));
437+
WebServiceClient.Builder builder = new WebServiceClient.Builder(6, "0123456789")
438+
.httpClient(customClient)
439+
.proxy(proxySelector);
440+
441+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, builder::build);
442+
assertEquals("Cannot set both httpClient and proxy. Configure proxy on the provided HttpClient instead.",
443+
ex.getMessage());
444+
}
445+
446+
@Test
447+
public void testHttpClientWithDefaultSettingsDoesNotThrow() throws Exception {
448+
HttpClient customClient = HttpClient.newBuilder()
449+
.connectTimeout(Duration.ofSeconds(10))
450+
.build();
451+
452+
// Should not throw because we're not setting connectTimeout or proxy
453+
WebServiceClient client = new WebServiceClient.Builder(6, "0123456789")
454+
.host("localhost")
455+
.port(8080)
456+
.disableHttps()
457+
.httpClient(customClient)
458+
.build();
459+
460+
assertNotNull(client);
461+
}
462+
410463
}

0 commit comments

Comments
 (0)