This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 60a145790 feat(sdk): implement builder pattern for client (#2245)
60a145790 is described below
commit 60a1457902af58d202ee3f8eb7460188481c0e59
Author: Chiradip Mandal <[email protected]>
AuthorDate: Sat Oct 11 11:24:03 2025 -0700
feat(sdk): implement builder pattern for client (#2245)
---
.../iggy/client/async/tcp/AsyncIggyTcpClient.java | 225 ++++++++++++++++
.../iggy/client/blocking/tcp/IggyTcpClient.java | 226 ++++++++++++++++
.../async/tcp/AsyncIggyTcpClientBuilderTest.java | 299 +++++++++++++++++++++
.../blocking/tcp/IggyTcpClientBuilderTest.java | 243 +++++++++++++++++
4 files changed, 993 insertions(+)
diff --git
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClient.java
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClient.java
index 966223417..6cada2006 100644
---
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClient.java
+++
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClient.java
@@ -25,6 +25,8 @@ import org.apache.iggy.client.async.StreamsClient;
import org.apache.iggy.client.async.TopicsClient;
import org.apache.iggy.client.async.UsersClient;
+import java.time.Duration;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/**
@@ -35,6 +37,12 @@ public class AsyncIggyTcpClient {
private final String host;
private final int port;
+ private final Optional<String> username;
+ private final Optional<String> password;
+ private final Optional<Duration> connectionTimeout;
+ private final Optional<Duration> requestTimeout;
+ private final Optional<Integer> connectionPoolSize;
+ private final Optional<RetryPolicy> retryPolicy;
private AsyncTcpConnection connection;
private MessagesClient messagesClient;
private ConsumerGroupsClient consumerGroupsClient;
@@ -43,8 +51,29 @@ public class AsyncIggyTcpClient {
private UsersClient usersClient;
public AsyncIggyTcpClient(String host, int port) {
+ this(host, port, null, null, null, null, null, null);
+ }
+
+ private AsyncIggyTcpClient(String host, int port, String username, String
password,
+ Duration connectionTimeout, Duration
requestTimeout,
+ Integer connectionPoolSize, RetryPolicy
retryPolicy) {
this.host = host;
this.port = port;
+ this.username = Optional.ofNullable(username);
+ this.password = Optional.ofNullable(password);
+ this.connectionTimeout = Optional.ofNullable(connectionTimeout);
+ this.requestTimeout = Optional.ofNullable(requestTimeout);
+ this.connectionPoolSize = Optional.ofNullable(connectionPoolSize);
+ this.retryPolicy = Optional.ofNullable(retryPolicy);
+ }
+
+ /**
+ * Creates a new builder for configuring AsyncIggyTcpClient.
+ *
+ * @return a new Builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
}
/**
@@ -59,6 +88,14 @@ public class AsyncIggyTcpClient {
streamsClient = new StreamsTcpClient(connection);
topicsClient = new TopicsTcpClient(connection);
usersClient = new UsersTcpClient(connection);
+ })
+ .thenCompose(v -> {
+ // Auto-login if credentials are provided
+ if (username.isPresent() && password.isPresent()) {
+ return usersClient.loginAsync(username.get(),
password.get())
+ .thenApply(identity -> null);
+ }
+ return CompletableFuture.completedFuture(null);
});
}
@@ -121,4 +158,192 @@ public class AsyncIggyTcpClient {
}
return CompletableFuture.completedFuture(null);
}
+
+ /**
+ * Builder for creating configured AsyncIggyTcpClient instances.
+ */
+ public static class Builder {
+ private String host = "localhost";
+ private Integer port = 8090;
+ private String username;
+ private String password;
+ private Duration connectionTimeout;
+ private Duration requestTimeout;
+ private Integer connectionPoolSize;
+ private RetryPolicy retryPolicy;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the host address for the Iggy server.
+ *
+ * @param host the host address
+ * @return this builder
+ */
+ public Builder host(String host) {
+ this.host = host;
+ return this;
+ }
+
+ /**
+ * Sets the port for the Iggy server.
+ *
+ * @param port the port number
+ * @return this builder
+ */
+ public Builder port(Integer port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Sets the credentials for authentication.
+ *
+ * @param username the username
+ * @param password the password
+ * @return this builder
+ */
+ public Builder credentials(String username, String password) {
+ this.username = username;
+ this.password = password;
+ return this;
+ }
+
+ /**
+ * Sets the connection timeout.
+ *
+ * @param connectionTimeout the connection timeout duration
+ * @return this builder
+ */
+ public Builder connectionTimeout(Duration connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the request timeout.
+ *
+ * @param requestTimeout the request timeout duration
+ * @return this builder
+ */
+ public Builder requestTimeout(Duration requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the connection pool size.
+ *
+ * @param connectionPoolSize the size of the connection pool
+ * @return this builder
+ */
+ public Builder connectionPoolSize(Integer connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Sets the retry policy.
+ *
+ * @param retryPolicy the retry policy to use
+ * @return this builder
+ */
+ public Builder retryPolicy(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ return this;
+ }
+
+ /**
+ * Builds and returns a configured AsyncIggyTcpClient instance.
+ * Note: You still need to call connect() on the returned client.
+ *
+ * @return a new AsyncIggyTcpClient instance
+ */
+ public AsyncIggyTcpClient build() {
+ if (host == null || host.isEmpty()) {
+ throw new IllegalArgumentException("Host cannot be null or
empty");
+ }
+ if (port == null || port <= 0) {
+ throw new IllegalArgumentException("Port must be a positive
integer");
+ }
+ return new AsyncIggyTcpClient(host, port, username, password,
+ connectionTimeout, requestTimeout, connectionPoolSize,
retryPolicy);
+ }
+ }
+
+ /**
+ * Retry policy for client operations.
+ */
+ public static class RetryPolicy {
+ private final int maxRetries;
+ private final Duration initialDelay;
+ private final Duration maxDelay;
+ private final double multiplier;
+
+ private RetryPolicy(int maxRetries, Duration initialDelay, Duration
maxDelay, double multiplier) {
+ this.maxRetries = maxRetries;
+ this.initialDelay = initialDelay;
+ this.maxDelay = maxDelay;
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * Creates a retry policy with exponential backoff.
+ *
+ * @return a RetryPolicy with exponential backoff configuration
+ */
+ public static RetryPolicy exponentialBackoff() {
+ return new RetryPolicy(3, Duration.ofMillis(100),
Duration.ofSeconds(5), 2.0);
+ }
+
+ /**
+ * Creates a retry policy with exponential backoff and custom
parameters.
+ *
+ * @param maxRetries the maximum number of retries
+ * @param initialDelay the initial delay before the first retry
+ * @param maxDelay the maximum delay between retries
+ * @param multiplier the multiplier for exponential backoff
+ * @return a RetryPolicy with custom exponential backoff configuration
+ */
+ public static RetryPolicy exponentialBackoff(int maxRetries, Duration
initialDelay, Duration maxDelay, double multiplier) {
+ return new RetryPolicy(maxRetries, initialDelay, maxDelay,
multiplier);
+ }
+
+ /**
+ * Creates a retry policy with fixed delay.
+ *
+ * @param maxRetries the maximum number of retries
+ * @param delay the fixed delay between retries
+ * @return a RetryPolicy with fixed delay configuration
+ */
+ public static RetryPolicy fixedDelay(int maxRetries, Duration delay) {
+ return new RetryPolicy(maxRetries, delay, delay, 1.0);
+ }
+
+ /**
+ * Creates a no-retry policy.
+ *
+ * @return a RetryPolicy that does not retry
+ */
+ public static RetryPolicy noRetry() {
+ return new RetryPolicy(0, Duration.ZERO, Duration.ZERO, 1.0);
+ }
+
+ public int getMaxRetries() {
+ return maxRetries;
+ }
+
+ public Duration getInitialDelay() {
+ return initialDelay;
+ }
+
+ public Duration getMaxDelay() {
+ return maxDelay;
+ }
+
+ public double getMultiplier() {
+ return multiplier;
+ }
+ }
}
diff --git
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/blocking/tcp/IggyTcpClient.java
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/blocking/tcp/IggyTcpClient.java
index ec48a3ed5..3e389057e 100644
---
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/blocking/tcp/IggyTcpClient.java
+++
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/blocking/tcp/IggyTcpClient.java
@@ -29,6 +29,8 @@ import org.apache.iggy.client.blocking.StreamsClient;
import org.apache.iggy.client.blocking.SystemClient;
import org.apache.iggy.client.blocking.TopicsClient;
import org.apache.iggy.client.blocking.UsersClient;
+import java.time.Duration;
+import java.util.Optional;
public class IggyTcpClient implements IggyBaseClient {
@@ -41,8 +43,31 @@ public class IggyTcpClient implements IggyBaseClient {
private final MessagesTcpClient messagesClient;
private final SystemTcpClient systemClient;
private final PersonalAccessTokensTcpClient personalAccessTokensClient;
+ private final String host;
+ private final Integer port;
+ private final Optional<String> username;
+ private final Optional<String> password;
+ private final Optional<Duration> connectionTimeout;
+ private final Optional<Duration> requestTimeout;
+ private final Optional<Integer> connectionPoolSize;
+ private final Optional<RetryPolicy> retryPolicy;
public IggyTcpClient(String host, Integer port) {
+ this(host, port, null, null, null, null, null, null);
+ }
+
+ private IggyTcpClient(String host, Integer port, String username, String
password,
+ Duration connectionTimeout, Duration requestTimeout,
+ Integer connectionPoolSize, RetryPolicy retryPolicy)
{
+ this.host = host;
+ this.port = port;
+ this.username = Optional.ofNullable(username);
+ this.password = Optional.ofNullable(password);
+ this.connectionTimeout = Optional.ofNullable(connectionTimeout);
+ this.requestTimeout = Optional.ofNullable(requestTimeout);
+ this.connectionPoolSize = Optional.ofNullable(connectionPoolSize);
+ this.retryPolicy = Optional.ofNullable(retryPolicy);
+
InternalTcpClient tcpClient = new InternalTcpClient(host, port);
tcpClient.connect();
usersClient = new UsersTcpClient(tcpClient);
@@ -54,6 +79,20 @@ public class IggyTcpClient implements IggyBaseClient {
messagesClient = new MessagesTcpClient(tcpClient);
systemClient = new SystemTcpClient(tcpClient);
personalAccessTokensClient = new
PersonalAccessTokensTcpClient(tcpClient);
+
+ // Auto-login if credentials are provided
+ if (this.username.isPresent() && this.password.isPresent()) {
+ usersClient.login(this.username.get(), this.password.get());
+ }
+ }
+
+ /**
+ * Creates a new builder for configuring IggyTcpClient.
+ *
+ * @return a new Builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
}
@Override
@@ -101,4 +140,191 @@ public class IggyTcpClient implements IggyBaseClient {
return personalAccessTokensClient;
}
+ /**
+ * Builder for creating configured IggyTcpClient instances.
+ */
+ public static class Builder {
+ private String host = "localhost";
+ private Integer port = 8090;
+ private String username;
+ private String password;
+ private Duration connectionTimeout;
+ private Duration requestTimeout;
+ private Integer connectionPoolSize;
+ private RetryPolicy retryPolicy;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the host address for the Iggy server.
+ *
+ * @param host the host address
+ * @return this builder
+ */
+ public Builder host(String host) {
+ this.host = host;
+ return this;
+ }
+
+ /**
+ * Sets the port for the Iggy server.
+ *
+ * @param port the port number
+ * @return this builder
+ */
+ public Builder port(Integer port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Sets the credentials for authentication.
+ *
+ * @param username the username
+ * @param password the password
+ * @return this builder
+ */
+ public Builder credentials(String username, String password) {
+ this.username = username;
+ this.password = password;
+ return this;
+ }
+
+ /**
+ * Sets the connection timeout.
+ *
+ * @param connectionTimeout the connection timeout duration
+ * @return this builder
+ */
+ public Builder connectionTimeout(Duration connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the request timeout.
+ *
+ * @param requestTimeout the request timeout duration
+ * @return this builder
+ */
+ public Builder requestTimeout(Duration requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the connection pool size.
+ *
+ * @param connectionPoolSize the size of the connection pool
+ * @return this builder
+ */
+ public Builder connectionPoolSize(Integer connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Sets the retry policy.
+ *
+ * @param retryPolicy the retry policy to use
+ * @return this builder
+ */
+ public Builder retryPolicy(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ return this;
+ }
+
+ /**
+ * Builds and returns a configured IggyTcpClient instance.
+ *
+ * @return a new IggyTcpClient instance
+ */
+ public IggyTcpClient build() {
+ if (host == null || host.isEmpty()) {
+ throw new IllegalArgumentException("Host cannot be null or
empty");
+ }
+ if (port == null || port <= 0) {
+ throw new IllegalArgumentException("Port must be a positive
integer");
+ }
+ return new IggyTcpClient(host, port, username, password,
+ connectionTimeout, requestTimeout, connectionPoolSize,
retryPolicy);
+ }
+ }
+
+ /**
+ * Retry policy for client operations.
+ */
+ public static class RetryPolicy {
+ private final int maxRetries;
+ private final Duration initialDelay;
+ private final Duration maxDelay;
+ private final double multiplier;
+
+ private RetryPolicy(int maxRetries, Duration initialDelay, Duration
maxDelay, double multiplier) {
+ this.maxRetries = maxRetries;
+ this.initialDelay = initialDelay;
+ this.maxDelay = maxDelay;
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * Creates a retry policy with exponential backoff.
+ *
+ * @return a RetryPolicy with exponential backoff configuration
+ */
+ public static RetryPolicy exponentialBackoff() {
+ return new RetryPolicy(3, Duration.ofMillis(100),
Duration.ofSeconds(5), 2.0);
+ }
+
+ /**
+ * Creates a retry policy with exponential backoff and custom
parameters.
+ *
+ * @param maxRetries the maximum number of retries
+ * @param initialDelay the initial delay before the first retry
+ * @param maxDelay the maximum delay between retries
+ * @param multiplier the multiplier for exponential backoff
+ * @return a RetryPolicy with custom exponential backoff configuration
+ */
+ public static RetryPolicy exponentialBackoff(int maxRetries, Duration
initialDelay, Duration maxDelay, double multiplier) {
+ return new RetryPolicy(maxRetries, initialDelay, maxDelay,
multiplier);
+ }
+
+ /**
+ * Creates a retry policy with fixed delay.
+ *
+ * @param maxRetries the maximum number of retries
+ * @param delay the fixed delay between retries
+ * @return a RetryPolicy with fixed delay configuration
+ */
+ public static RetryPolicy fixedDelay(int maxRetries, Duration delay) {
+ return new RetryPolicy(maxRetries, delay, delay, 1.0);
+ }
+
+ /**
+ * Creates a no-retry policy.
+ *
+ * @return a RetryPolicy that does not retry
+ */
+ public static RetryPolicy noRetry() {
+ return new RetryPolicy(0, Duration.ZERO, Duration.ZERO, 1.0);
+ }
+
+ public int getMaxRetries() {
+ return maxRetries;
+ }
+
+ public Duration getInitialDelay() {
+ return initialDelay;
+ }
+
+ public Duration getMaxDelay() {
+ return maxDelay;
+ }
+
+ public double getMultiplier() {
+ return multiplier;
+ }
+ }
+
}
diff --git
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClientBuilderTest.java
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClientBuilderTest.java
new file mode 100644
index 000000000..76fe97cdd
--- /dev/null
+++
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/AsyncIggyTcpClientBuilderTest.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.iggy.client.async.tcp;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.AfterEach;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests for AsyncIggyTcpClient builder pattern.
+ * Tests the builder functionality against a running Iggy server.
+ */
+class AsyncIggyTcpClientBuilderTest {
+
+ private static final String HOST = "127.0.0.1";
+ private static final int PORT = 8090;
+
+ private AsyncIggyTcpClient client;
+
+ @AfterEach
+ void cleanup() throws Exception {
+ if (client != null) {
+ client.close().get(5, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ void shouldCreateClientWithBuilder() throws Exception {
+ // Given: Builder with basic configuration
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Client should be connected and functional
+ assertNotNull(client.users());
+ assertNotNull(client.messages());
+ assertNotNull(client.streams());
+ assertNotNull(client.topics());
+ assertNotNull(client.consumerGroups());
+ }
+
+ @Test
+ void shouldCreateClientWithCredentials() throws Exception {
+ // Given: Builder with credentials configured
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect (auto-login should happen)
+ CompletableFuture<Void> connectFuture = client.connect();
+ connectFuture.get(5, TimeUnit.SECONDS);
+
+ // Then: Should be connected and logged in
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithTimeoutConfiguration() throws Exception {
+ // Given: Builder with timeout configuration
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .connectionTimeout(Duration.ofSeconds(30))
+ .requestTimeout(Duration.ofSeconds(10))
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithConnectionPoolSize() throws Exception {
+ // Given: Builder with connection pool size
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .connectionPoolSize(10)
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithRetryPolicy() throws Exception {
+ // Given: Builder with exponential backoff retry policy
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+
.retryPolicy(AsyncIggyTcpClient.RetryPolicy.exponentialBackoff())
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithCustomRetryPolicy() throws Exception {
+ // Given: Builder with custom retry policy
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .retryPolicy(AsyncIggyTcpClient.RetryPolicy.fixedDelay(5,
Duration.ofMillis(500)))
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithNoRetryPolicy() throws Exception {
+ // Given: Builder with no retry policy
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .retryPolicy(AsyncIggyTcpClient.RetryPolicy.noRetry())
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldCreateClientWithAllOptions() throws Exception {
+ // Given: Builder with all configuration options
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .credentials("iggy", "iggy")
+ .connectionTimeout(Duration.ofSeconds(30))
+ .requestTimeout(Duration.ofSeconds(10))
+ .connectionPoolSize(10)
+ .retryPolicy(AsyncIggyTcpClient.RetryPolicy.exponentialBackoff(
+ 3, Duration.ofMillis(100), Duration.ofSeconds(5), 2.0))
+ .build();
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldUseDefaultValues() throws Exception {
+ // Given: Builder with only credentials (should use defaults)
+ client = AsyncIggyTcpClient.builder()
+ .credentials("iggy", "iggy")
+ .build(); // Uses default host=localhost, port=8090
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should succeed
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldThrowExceptionForEmptyHost() {
+ // Given: Builder with empty host
+ AsyncIggyTcpClient.Builder builder = AsyncIggyTcpClient.builder()
+ .host("")
+ .port(PORT);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForNullHost() {
+ // Given: Builder with null host
+ AsyncIggyTcpClient.Builder builder = AsyncIggyTcpClient.builder()
+ .host(null)
+ .port(PORT);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidPort() {
+ // Given: Builder with invalid port
+ AsyncIggyTcpClient.Builder builder = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(-1);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForZeroPort() {
+ // Given: Builder with zero port
+ AsyncIggyTcpClient.Builder builder = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(0);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldMaintainBackwardCompatibilityWithOldConstructor() throws
Exception {
+ // Given: Old constructor approach
+ client = new AsyncIggyTcpClient(HOST, PORT);
+
+ // When: Connect to server
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should work as before
+ assertNotNull(client.users());
+ }
+
+ @Test
+ void shouldConnectAndPerformOperations() throws Exception {
+ // Given: Client with credentials
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Connect
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // Then: Should be able to access all clients
+ assertNotNull(client.users(), "Users client should not be null");
+ assertNotNull(client.messages(), "Messages client should not be null");
+ assertNotNull(client.streams(), "Streams client should not be null");
+ assertNotNull(client.topics(), "Topics client should not be null");
+ assertNotNull(client.consumerGroups(), "Consumer groups client should
not be null");
+ }
+
+ @Test
+ void shouldCloseConnectionGracefully() throws Exception {
+ // Given: Connected client
+ client = AsyncIggyTcpClient.builder()
+ .host(HOST)
+ .port(PORT)
+ .credentials("iggy", "iggy")
+ .build();
+ client.connect().get(5, TimeUnit.SECONDS);
+
+ // When: Close connection
+ CompletableFuture<Void> closeFuture = client.close();
+ closeFuture.get(5, TimeUnit.SECONDS);
+
+ // Then: Should complete without exception
+ assertTrue(closeFuture.isDone());
+ assertFalse(closeFuture.isCompletedExceptionally());
+ }
+}
diff --git
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/IggyTcpClientBuilderTest.java
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/IggyTcpClientBuilderTest.java
new file mode 100644
index 000000000..ac47a1bd8
--- /dev/null
+++
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/IggyTcpClientBuilderTest.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.iggy.client.blocking.tcp;
+
+import org.apache.iggy.client.blocking.IntegrationTest;
+import org.apache.iggy.client.blocking.IggyBaseClient;
+import org.apache.iggy.system.ClientInfo;
+import org.junit.jupiter.api.Test;
+import java.time.Duration;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests for IggyTcpClient builder pattern.
+ * Tests the builder functionality against a running Iggy server.
+ */
+class IggyTcpClientBuilderTest extends IntegrationTest {
+
+ @Override
+ protected IggyBaseClient getClient() {
+ return TcpClientFactory.create(iggyServer);
+ }
+
+ @Test
+ void shouldCreateClientWithBuilder() {
+ // Given: Builder with basic configuration
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .build();
+
+ // When: Login to verify connection
+ client.users().login("iggy", "iggy");
+
+ // Then: Client should be able to fetch system info
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithCredentials() {
+ // Given: Builder with credentials configured
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Try to access system info (auto-login should have happened)
+ // Then: Should succeed without explicit login
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithTimeoutConfiguration() {
+ // Given: Builder with timeout configuration
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .connectionTimeout(Duration.ofSeconds(30))
+ .requestTimeout(Duration.ofSeconds(10))
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithConnectionPoolSize() {
+ // Given: Builder with connection pool size
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .connectionPoolSize(10)
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithRetryPolicy() {
+ // Given: Builder with exponential backoff retry policy
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .retryPolicy(IggyTcpClient.RetryPolicy.exponentialBackoff())
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithCustomRetryPolicy() {
+ // Given: Builder with custom retry policy
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .retryPolicy(IggyTcpClient.RetryPolicy.fixedDelay(5,
Duration.ofMillis(500)))
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithNoRetryPolicy() {
+ // Given: Builder with no retry policy
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .retryPolicy(IggyTcpClient.RetryPolicy.noRetry())
+ .credentials("iggy", "iggy")
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldCreateClientWithAllOptions() {
+ // Given: Builder with all configuration options
+ IggyTcpClient client = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(TCP_PORT)
+ .credentials("iggy", "iggy")
+ .connectionTimeout(Duration.ofSeconds(30))
+ .requestTimeout(Duration.ofSeconds(10))
+ .connectionPoolSize(10)
+ .retryPolicy(IggyTcpClient.RetryPolicy.exponentialBackoff(
+ 3, Duration.ofMillis(100), Duration.ofSeconds(5), 2.0))
+ .build();
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldUseDefaultValues() {
+ // Given: Builder with only required fields (should use defaults)
+ IggyTcpClient client = IggyTcpClient.builder()
+ .credentials("iggy", "iggy")
+ .build(); // Uses default host=localhost, port=8090
+
+ // When: Perform an operation
+ // Then: Should succeed
+ List<ClientInfo> clients = client.system().getClients();
+ assertNotNull(clients);
+ }
+
+ @Test
+ void shouldThrowExceptionForEmptyHost() {
+ // Given: Builder with empty host
+ IggyTcpClient.Builder builder = IggyTcpClient.builder()
+ .host("")
+ .port(TCP_PORT);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForNullHost() {
+ // Given: Builder with null host
+ IggyTcpClient.Builder builder = IggyTcpClient.builder()
+ .host(null)
+ .port(TCP_PORT);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidPort() {
+ // Given: Builder with invalid port
+ IggyTcpClient.Builder builder = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(-1);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldThrowExceptionForZeroPort() {
+ // Given: Builder with zero port
+ IggyTcpClient.Builder builder = IggyTcpClient.builder()
+ .host("127.0.0.1")
+ .port(0);
+
+ // When/Then: Building should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void shouldMaintainBackwardCompatibilityWithOldConstructor() {
+ // Given: Old constructor approach
+ IggyTcpClient client = new IggyTcpClient("127.0.0.1", TCP_PORT);
+
+ // When: Login and perform operation
+ client.users().login("iggy", "iggy");
+ List<ClientInfo> clients = client.system().getClients();
+
+ // Then: Should work as before
+ assertNotNull(clients);
+ }
+}