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);
+    }
+}


Reply via email to