This is an automated email from the ASF dual-hosted git repository.

maciej 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 a3328c8a2 feat(java): Increase test coverage (#2939)
a3328c8a2 is described below

commit a3328c8a2436926540deb119de95af45fa6d9465
Author: Jonathon Henderson <[email protected]>
AuthorDate: Mon Mar 16 12:43:57 2026 +0000

    feat(java): Increase test coverage (#2939)
    
    Closes #2938
---
 .../org/apache/iggy/exception/IggyException.java   |   9 -
 .../org/apache/iggy/system/ClientInfoDetails.java  |   3 +-
 .../apache/iggy/topic/CompressionAlgorithm.java    |   2 +-
 .../org/apache/iggy/config/RetryPolicyTest.java    |  96 ++++++++++
 .../consumergroup/ConsumerGroupDetailsTest.java}   |  36 ++--
 .../apache/iggy/consumergroup/ConsumerTest.java    |  74 ++++++++
 .../exception/IggyAuthenticationExceptionTest.java |  77 ++++++++
 .../exception/IggyAuthorizationExceptionTest.java  |  61 +++++++
 .../iggy/exception/IggyClientExceptionTest.java}   |  33 ++--
 .../iggy/exception/IggyConflictExceptionTest.java  |  75 ++++++++
 .../IggyConnectionClosedExceptionTest.java         |  51 ++++++
 .../exception/IggyConnectionExceptionTest.java}    |  33 ++--
 .../exception/IggyEmptyResponseExceptionTest.java} |  31 ++--
 .../apache/iggy/exception/IggyErrorCodeTest.java   |  89 +++++++++
 .../IggyInvalidArgumentExceptionTest.java}         |  34 ++--
 .../IggyMalformedResponseExceptionTest.java}       |  33 ++--
 .../IggyMissingCredentialsExceptionTest.java}      |  33 ++--
 .../exception/IggyNotConnectedExceptionTest.java}  |  31 ++--
 .../IggyOperationNotSupportedExceptionTest.java    |  60 +++++++
 .../iggy/exception/IggyProtocolExceptionTest.java} |  33 ++--
 .../IggyResourceNotFoundExceptionTest.java         |  89 +++++++++
 .../iggy/exception/IggyServerExceptionTest.java    | 200 +++++++++++++++++++++
 .../iggy/exception/IggyTimeoutExceptionTest.java   |  50 ++++++
 .../iggy/exception/IggyTlsExceptionTest.java}      |  33 ++--
 .../exception/IggyValidationExceptionTest.java     |  97 ++++++++++
 .../apache/iggy/identifier/BaseIdentifierTest.java | 131 ++++++++++++++
 .../apache/iggy/identifier/ConsumerIdTest.java}    |  29 +--
 .../apache/iggy/identifier/IdentifierTest.java}    |  29 ++-
 .../org/apache/iggy/identifier/StreamIdTest.java}  |  29 +--
 .../org/apache/iggy/identifier/TopicIdTest.java}   |  29 +--
 .../org/apache/iggy/identifier/UserIdTest.java}    |  29 +--
 .../apache/iggy/serde/BytesDeserializerTest.java   |  26 +++
 .../org/apache/iggy/stream/StreamDetailsTest.java  |  57 ++++++
 .../apache/iggy/system/ClientInfoDetailsTest.java  |  44 +++++
 .../iggy/topic/CompressionAlgorithmTest.java       |  50 ++++++
 .../org/apache/iggy/topic/TopicDetailsTest.java    |  60 +++++++
 .../org/apache/iggy/user/UserInfoDetailsTest.java  |  45 +++++
 .../java/org/apache/iggy/user/UserStatusTest.java  |  47 +++++
 38 files changed, 1674 insertions(+), 294 deletions(-)

diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/exception/IggyException.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/exception/IggyException.java
index e997cf57c..ce10a515d 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/exception/IggyException.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/exception/IggyException.java
@@ -46,13 +46,4 @@ public abstract class IggyException extends RuntimeException 
{
     protected IggyException(String message, Throwable cause) {
         super(message, cause);
     }
-
-    /**
-     * Constructs a new IggyException with the specified cause.
-     *
-     * @param cause the cause of the exception
-     */
-    protected IggyException(Throwable cause) {
-        super(cause);
-    }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
index f87b69d70..d5b23edfb 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
@@ -19,7 +19,6 @@
 
 package org.apache.iggy.system;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -30,7 +29,7 @@ public record ClientInfoDetails(
         String transport,
         Long consumerGroupsCount,
         List<ConsumerGroupInfo> consumerGroups) {
-    public ClientInfoDetails(ClientInfo clientInfo, 
ArrayList<ConsumerGroupInfo> consumerGroups) {
+    public ClientInfoDetails(ClientInfo clientInfo, List<ConsumerGroupInfo> 
consumerGroups) {
         this(
                 clientInfo.clientId(),
                 clientInfo.userId(),
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
index 7335bfe78..ef5ba7301 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
@@ -31,7 +31,7 @@ public enum CompressionAlgorithm {
         this.code = code;
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
+    public static CompressionAlgorithm fromCode(int code) {
         for (CompressionAlgorithm algorithm : values()) {
             if (algorithm.code == code) {
                 return algorithm;
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/config/RetryPolicyTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/config/RetryPolicyTest.java
new file mode 100644
index 000000000..f1c7a994f
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/config/RetryPolicyTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.config;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RetryPolicyTest {
+    @Test
+    void exponentialBackoffWithNoArgsReturnsExpectedRetryPolicy() {
+        var retryPolicy = RetryPolicy.exponentialBackoff();
+
+        assertThat(retryPolicy.getMaxRetries()).isEqualTo(3);
+        
assertThat(retryPolicy.getInitialDelay()).isEqualTo(Duration.ofMillis(100));
+        assertThat(retryPolicy.getMaxDelay()).isEqualTo(Duration.ofSeconds(5));
+        assertThat(retryPolicy.getMultiplier()).isEqualTo(2.0);
+    }
+
+    @Test
+    void exponentialBackoffWithAllArgsReturnsExpectedRetryPolicy() {
+        var retryPolicy = RetryPolicy.exponentialBackoff(30, 
Duration.ofMillis(50), Duration.ofMinutes(60), 1.5);
+
+        assertThat(retryPolicy.getMaxRetries()).isEqualTo(30);
+        
assertThat(retryPolicy.getInitialDelay()).isEqualTo(Duration.ofMillis(50));
+        
assertThat(retryPolicy.getMaxDelay()).isEqualTo(Duration.ofMinutes(60));
+        assertThat(retryPolicy.getMultiplier()).isEqualTo(1.5);
+    }
+
+    @Test
+    void fixedDelayReturnsExpectedRetryPolicy() {
+        var retryPolicy = RetryPolicy.fixedDelay(50, Duration.ofMillis(500));
+
+        assertThat(retryPolicy.getMaxRetries()).isEqualTo(50);
+        
assertThat(retryPolicy.getInitialDelay()).isEqualTo(Duration.ofMillis(500));
+        
assertThat(retryPolicy.getMaxDelay()).isEqualTo(Duration.ofMillis(500));
+        assertThat(retryPolicy.getMultiplier()).isEqualTo(1.0);
+    }
+
+    @Test
+    void noRetryReturnsExpectedRetryPolicy() {
+        var retryPolicy = RetryPolicy.noRetry();
+
+        assertThat(retryPolicy.getMaxRetries()).isEqualTo(0);
+        assertThat(retryPolicy.getInitialDelay()).isEqualTo(Duration.ZERO);
+        assertThat(retryPolicy.getMaxDelay()).isEqualTo(Duration.ZERO);
+        assertThat(retryPolicy.getMultiplier()).isEqualTo(1.0);
+    }
+
+    @Test
+    void getMaxRetriesReturnsMaxRetries() {
+        var retryPolicy = RetryPolicy.exponentialBackoff(2, 
Duration.ofMillis(200), Duration.ofMillis(1000), 1.0);
+
+        assertThat(retryPolicy.getMaxRetries()).isEqualTo(2);
+    }
+
+    @Test
+    void getInitialDelayReturnsInitialDelay() {
+        var retryPolicy = RetryPolicy.exponentialBackoff(2, 
Duration.ofMillis(200), Duration.ofMillis(1000), 1.0);
+
+        
assertThat(retryPolicy.getInitialDelay()).isEqualTo(Duration.ofMillis(200));
+    }
+
+    @Test
+    void getMaxDelayReturnsMaxDelay() {
+        var retryPolicy = RetryPolicy.exponentialBackoff(2, 
Duration.ofMillis(200), Duration.ofMillis(1000), 1.0);
+
+        
assertThat(retryPolicy.getMaxDelay()).isEqualTo(Duration.ofMillis(1000));
+    }
+
+    @Test
+    void getMultiplierReturnsMultiplier() {
+        var retryPolicy = RetryPolicy.exponentialBackoff(2, 
Duration.ofMillis(200), Duration.ofMillis(1000), 1.0);
+
+        assertThat(retryPolicy.getMultiplier()).isEqualTo(1.0);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerGroupDetailsTest.java
similarity index 50%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerGroupDetailsTest.java
index f87b69d70..70f6ade1c 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/system/ClientInfoDetails.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerGroupDetailsTest.java
@@ -17,26 +17,26 @@
  * under the License.
  */
 
-package org.apache.iggy.system;
+package org.apache.iggy.consumergroup;
+
+import org.junit.jupiter.api.Test;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
-public record ClientInfoDetails(
-        Long clientId,
-        Optional<Long> userId,
-        String address,
-        String transport,
-        Long consumerGroupsCount,
-        List<ConsumerGroupInfo> consumerGroups) {
-    public ClientInfoDetails(ClientInfo clientInfo, 
ArrayList<ConsumerGroupInfo> consumerGroups) {
-        this(
-                clientInfo.clientId(),
-                clientInfo.userId(),
-                clientInfo.address(),
-                clientInfo.transport(),
-                clientInfo.consumerGroupsCount(),
-                consumerGroups);
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ConsumerGroupDetailsTest {
+    @Test
+    void constructorWithConsumerGroupCreatesExpectedConsumerGroupDetails() {
+        var consumerGroup = new ConsumerGroup(2L, "group", 3L, 1L);
+        var members = List.of(new ConsumerGroupMember(1L, 3L, List.of()));
+
+        var consumerGroupDetails = new ConsumerGroupDetails(consumerGroup, 
members);
+
+        assertThat(consumerGroupDetails.id()).isEqualTo(2L);
+        assertThat(consumerGroupDetails.name()).isEqualTo("group");
+        assertThat(consumerGroupDetails.partitionsCount()).isEqualTo(3);
+        assertThat(consumerGroupDetails.membersCount()).isEqualTo(1L);
+        assertThat(consumerGroupDetails.members()).isEqualTo(members);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerTest.java
new file mode 100644
index 000000000..2f0685567
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/consumergroup/ConsumerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.consumergroup;
+
+import org.apache.iggy.consumergroup.Consumer.Kind;
+import org.apache.iggy.identifier.ConsumerId;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ConsumerTest {
+    @Test
+    void ofWithNumericIdCreatesExpectedConsumerWithConsumerKind() {
+        var consumer = Consumer.of(123L);
+
+        assertThat(consumer.id().getId()).isEqualTo(123L);
+        assertThat(consumer.kind()).isEqualTo(Kind.Consumer);
+    }
+
+    @Test
+    void ofWithConsumerIdCreatesExpectedConsumerWithConsumerKind() {
+        var consumer = Consumer.of(ConsumerId.of(321L));
+
+        assertThat(consumer.id().getId()).isEqualTo(321L);
+        assertThat(consumer.kind()).isEqualTo(Consumer.Kind.Consumer);
+    }
+
+    @Test
+    void groupWithNumericIdCreatesExpectedConsumerWithConsumerGroupKind() {
+        var consumer = Consumer.group(456L);
+
+        assertThat(consumer.id().getId()).isEqualTo(456L);
+        assertThat(consumer.kind()).isEqualTo(Consumer.Kind.ConsumerGroup);
+    }
+
+    @Test
+    void groupWithConsumerIdCreatesExpectedConsumerWithConsumerGroupKind() {
+        var consumer = Consumer.group(ConsumerId.of(654L));
+
+        assertThat(consumer.id().getId()).isEqualTo(654L);
+        assertThat(consumer.kind()).isEqualTo(Consumer.Kind.ConsumerGroup);
+    }
+
+    @Nested
+    class ConsumerKindTest {
+        @ParameterizedTest
+        @CsvSource({"Consumer, 1", "ConsumerGroup, 2"})
+        void asCodeReturnsExpectedCode(Consumer.Kind kind, int expectedCode) {
+            var code = kind.asCode();
+
+            assertThat(code).isEqualTo(expectedCode);
+        }
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthenticationExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthenticationExceptionTest.java
new file mode 100644
index 000000000..4383c92dc
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthenticationExceptionTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyAuthenticationExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyAuthenticationException() {
+        var exception = new IggyAuthenticationException(
+                IggyErrorCode.UNAUTHENTICATED, 40, "unauthenticated", 
Optional.of("foo"), Optional.of("bar"));
+
+        assertThat(exception).isInstanceOf(IggyAuthenticationException.class);
+        
assertThat(exception.getErrorCode()).isEqualTo(IggyErrorCode.UNAUTHENTICATED);
+        assertThat(exception.getRawErrorCode()).isEqualTo(40);
+        assertThat(exception.getReason()).isEqualTo("unauthenticated");
+        assertThat(exception.getField()).isEqualTo(Optional.of("foo"));
+        assertThat(exception.getErrorId()).isEqualTo(Optional.of("bar"));
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "UNAUTHENTICATED",
+                "INVALID_CREDENTIALS",
+                "INVALID_USERNAME",
+                "INVALID_PASSWORD",
+                "INVALID_PAT_TOKEN",
+                "PASSWORD_DOES_NOT_MATCH",
+                "PASSWORD_HASH_INTERNAL_ERROR"
+            })
+    void matchesReturnsTrueForAuthenticationRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyAuthenticationException.matches(code)).isTrue();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "UNAUTHENTICATED",
+                "INVALID_CREDENTIALS",
+                "INVALID_USERNAME",
+                "INVALID_PASSWORD",
+                "INVALID_PAT_TOKEN",
+                "PASSWORD_DOES_NOT_MATCH",
+                "PASSWORD_HASH_INTERNAL_ERROR"
+            },
+            mode = Mode.EXCLUDE)
+    void matchesReturnsFalseForNonAuthenticationRelatedCodes(IggyErrorCode 
code) {
+        assertThat(IggyAuthenticationException.matches(code)).isFalse();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthorizationExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthorizationExceptionTest.java
new file mode 100644
index 000000000..fbce2144c
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyAuthorizationExceptionTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyAuthorizationExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyAuthorizationException() {
+        var exception = new IggyAuthorizationException(
+                IggyErrorCode.UNAUTHORIZED, 41, "unauthorized", 
Optional.of("bar"), Optional.of("foo"));
+
+        assertThat(exception).isInstanceOf(IggyAuthorizationException.class);
+        
assertThat(exception.getErrorCode()).isEqualTo(IggyErrorCode.UNAUTHORIZED);
+        assertThat(exception.getRawErrorCode()).isEqualTo(41);
+        assertThat(exception.getReason()).isEqualTo("unauthorized");
+        assertThat(exception.getField()).isEqualTo(Optional.of("bar"));
+        assertThat(exception.getErrorId()).isEqualTo(Optional.of("foo"));
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {"UNAUTHORIZED"})
+    void matchesReturnsTrueForAuthorizationRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyAuthorizationException.matches(code)).isTrue();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {"UNAUTHORIZED"},
+            mode = Mode.EXCLUDE)
+    void matchesReturnsFalseForNonAuthorizationRelatedCodes(IggyErrorCode 
code) {
+        assertThat(IggyAuthorizationException.matches(code)).isFalse();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyClientExceptionTest.java
similarity index 52%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyClientExceptionTest.java
index 7335bfe78..4dd3a558d 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyClientExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyClientExceptionTest {
+    @Test
+    void constructorWithMessageCreatesExpectedIggyClientException() {
+        var exception = new IggyClientException("message");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void constructorWithMessageAndCauseCreatesExpectedIggyClientException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyClientException("message", cause);
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConflictExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConflictExceptionTest.java
new file mode 100644
index 000000000..36e22f945
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConflictExceptionTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyConflictExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyConflictException() {
+        var exception = new IggyConflictException(
+                IggyErrorCode.USER_ALREADY_EXISTS, 46, "userAlreadyExists", 
Optional.of("foo"), Optional.of("bar"));
+
+        assertThat(exception).isInstanceOf(IggyConflictException.class);
+        
assertThat(exception.getErrorCode()).isEqualTo(IggyErrorCode.USER_ALREADY_EXISTS);
+        assertThat(exception.getRawErrorCode()).isEqualTo(46);
+        assertThat(exception.getReason()).isEqualTo("userAlreadyExists");
+        assertThat(exception.getField()).isEqualTo(Optional.of("foo"));
+        assertThat(exception.getErrorId()).isEqualTo(Optional.of("bar"));
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "USER_ALREADY_EXISTS",
+                "CLIENT_ALREADY_EXISTS",
+                "STREAM_ALREADY_EXISTS",
+                "TOPIC_ALREADY_EXISTS",
+                "CONSUMER_GROUP_ALREADY_EXISTS",
+                "PAT_NAME_ALREADY_EXISTS"
+            })
+    void matchesReturnsTrueForConflictRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyConflictException.matches(code)).isTrue();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "USER_ALREADY_EXISTS",
+                "CLIENT_ALREADY_EXISTS",
+                "STREAM_ALREADY_EXISTS",
+                "TOPIC_ALREADY_EXISTS",
+                "CONSUMER_GROUP_ALREADY_EXISTS",
+                "PAT_NAME_ALREADY_EXISTS"
+            },
+            mode = Mode.EXCLUDE)
+    void matchesReturnsFalseForNonConflictRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyConflictException.matches(code)).isFalse();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionClosedExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionClosedExceptionTest.java
new file mode 100644
index 000000000..cccfe7330
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionClosedExceptionTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyConnectionClosedExceptionTest {
+    @Test
+    void constructorWithNoArgsCreatesExpectedIggyConnectionClosedException() {
+        var exception = new IggyConnectionClosedException();
+
+        assertThat(exception.getMessage()).isEqualToIgnoringCase("Connection 
has been closed");
+        assertThat(exception.getCause()).isNull();
+    }
+
+    @Test
+    void constructorWithMessageCreatesExpectedIggyConnectionClosedException() {
+        var exception = new IggyConnectionClosedException("message");
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
+    }
+
+    @Test
+    void 
constructorWithMessageAndCauseCreatesExpectedIggyConnectionClosedException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyConnectionClosedException("message", cause);
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionExceptionTest.java
similarity index 52%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionExceptionTest.java
index 7335bfe78..508f3bf7b 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyConnectionExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyConnectionExceptionTest {
+    @Test
+    void constructorWithMessageCreatesExpectedIggyConnectionException() {
+        var exception = new IggyConnectionException("message");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void 
constructorWithMessageAndCauseCreatesExpectedIggyConnectionException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyConnectionException("message", cause);
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyEmptyResponseExceptionTest.java
similarity index 56%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyEmptyResponseExceptionTest.java
index 7335bfe78..d63d88fd4 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyEmptyResponseExceptionTest.java
@@ -17,30 +17,25 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyEmptyResponseExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyEmptyResponseException() {
+        var exception = new IggyEmptyResponseException("CMD");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).startsWithIgnoringCase("Received an 
empty response for command: ");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void getCommandReturnsProvidedCommand() {
+        var exception = new IggyEmptyResponseException("CMD");
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getCommand()).isEqualTo("CMD");
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyErrorCodeTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyErrorCodeTest.java
index b4d5afb83..3cb4c5118 100644
--- 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyErrorCodeTest.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyErrorCodeTest.java
@@ -21,6 +21,9 @@ package org.apache.iggy.exception;
 
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -146,4 +149,90 @@ class IggyErrorCodeTest {
             assertThat(result).isEqualTo(IggyErrorCode.ERROR);
         }
     }
+
+    @ParameterizedTest
+    @CsvSource({
+        // General errors
+        "1, ERROR",
+        "3, INVALID_COMMAND",
+        "4, INVALID_FORMAT",
+        "6, FEATURE_UNAVAILABLE",
+        "7, CANNOT_PARSE_INT",
+        "8, CANNOT_PARSE_SLICE",
+        "9, CANNOT_PARSE_UTF8",
+
+        // Resource errors
+        "20, RESOURCE_NOT_FOUND",
+        "100, CANNOT_LOAD_RESOURCE",
+
+        // Authentication/Authorization errors
+        "40, UNAUTHENTICATED",
+        "41, UNAUTHORIZED",
+        "42, INVALID_CREDENTIALS",
+        "43, INVALID_USERNAME",
+        "44, INVALID_PASSWORD",
+        "45, CLEAR_TEXT_PASSWORD_REQUIRED",
+        "46, USER_ALREADY_EXISTS",
+        "47, USER_INACTIVE",
+        "48, CANNOT_DELETE_USER_WITH_ACTIVE_PAT",
+        "49, CANNOT_UPDATE_OWN_PERMISSIONS",
+        "50, CANNOT_DELETE_YOURSELF",
+        "51, CLIENT_ALREADY_EXISTS",
+        "52, CLIENT_NOT_FOUND",
+        "53, INVALID_PAT_TOKEN",
+        "54, PAT_NAME_ALREADY_EXISTS",
+        "77, PASSWORD_DOES_NOT_MATCH",
+        "78, PASSWORD_HASH_INTERNAL_ERROR",
+
+        // Stream errors
+        "1009, STREAM_ID_NOT_FOUND",
+        "1010, STREAM_NAME_NOT_FOUND",
+        "1012, STREAM_ALREADY_EXISTS",
+        "1013, INVALID_STREAM_NAME",
+        "1014, CANNOT_CREATE_STREAM_DIRECTORY",
+
+        // Topic errors
+        "2010, TOPIC_ID_NOT_FOUND",
+        "2011, TOPIC_NAME_NOT_FOUND",
+        "2012, TOPICS_COUNT_EXCEEDED",
+        "2013, TOPIC_ALREADY_EXISTS",
+        "2014, INVALID_TOPIC_NAME",
+        "2015, INVALID_REPLICATION_FACTOR",
+        "2016, CANNOT_CREATE_TOPIC_DIRECTORY",
+
+        // Partition errors
+        "3007, PARTITION_NOT_FOUND",
+
+        // Consumer group errors
+        "5000, CONSUMER_GROUP_ID_NOT_FOUND",
+        "5002, CONSUMER_GROUP_MEMBER_NOT_FOUND",
+        "5003, CONSUMER_GROUP_NAME_NOT_FOUND",
+        "5004, CONSUMER_GROUP_ALREADY_EXISTS",
+        "5005, INVALID_CONSUMER_GROUP_NAME",
+        "5006, CONSUMER_GROUP_NOT_JOINED",
+
+        // Segment errors
+        "4000, SEGMENT_NOT_FOUND",
+        "4001, SEGMENT_CLOSED",
+        "4002, CANNOT_READ_SEGMENT",
+        "4003, CANNOT_SAVE_SEGMENT",
+
+        // Message errors
+        "7000, TOO_MANY_MESSAGES",
+        "7001, EMPTY_MESSAGES",
+        "7002, TOO_BIG_MESSAGE",
+        "7003, INVALID_MESSAGE_CHECKSUM",
+        "7004, MESSAGE_NOT_FOUND",
+    })
+    void fromCodeReturnsExpectedIggyErrorCodeWhenCodeIsValid(int code, 
IggyErrorCode expected) {
+        var iggyErrorCode = IggyErrorCode.fromCode(code);
+
+        assertThat(iggyErrorCode).isEqualTo(expected);
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 123456789, 2099})
+    void fromCodeReturnsUnknownIggyErrorCodeWhenCodeIsInvalid(int code) {
+        
assertThat(IggyErrorCode.fromCode(code)).isEqualTo(IggyErrorCode.UNKNOWN);
+    }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyInvalidArgumentExceptionTest.java
similarity index 51%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyInvalidArgumentExceptionTest.java
index 7335bfe78..e802d04b7 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyInvalidArgumentExceptionTest.java
@@ -17,30 +17,28 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyInvalidArgumentExceptionTest {
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
-    }
+    @Test
+    void constructorWithMessageCreatesExpectedIggyInvalidArgumentException() {
+        var exception = new IggyInvalidArgumentException("message");
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public Integer asCode() {
-        return code;
+    @Test
+    void 
constructorWithMessageAndCauseCreatesExpectedIggyInvalidArgumentException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyInvalidArgumentException("message", cause);
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMalformedResponseExceptionTest.java
similarity index 50%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMalformedResponseExceptionTest.java
index 7335bfe78..e87d7902d 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMalformedResponseExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyMalformedResponseExceptionTest {
+    @Test
+    void constructorWithMessageCreatesExpectedIggyMalformedResponseException() 
{
+        var exception = new IggyMalformedResponseException("message");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void 
constructorWithMessageAndCauseCreatesExpectedIggyMalformedResponseException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyMalformedResponseException("message", cause);
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMissingCredentialsExceptionTest.java
similarity index 50%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMissingCredentialsExceptionTest.java
index 7335bfe78..cb8b1dd35 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyMissingCredentialsExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyMissingCredentialsExceptionTest {
+    @Test
+    void constructorWithNoArgsCreatesExpectedIggyMissingCredentialsException() 
{
+        var exception = new IggyMissingCredentialsException();
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage())
+                .isEqualToIgnoringCase(
+                        "No credentials provided. Use login(username, 
password) or provide credentials when building the client.");
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void 
constructorWithMessageCreatesExpectedIggyMissingCredentialsException() {
+        var exception = new IggyMissingCredentialsException("message");
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyNotConnectedExceptionTest.java
similarity index 54%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyNotConnectedExceptionTest.java
index 7335bfe78..9c05bb8c1 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyNotConnectedExceptionTest.java
@@ -17,30 +17,25 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyNotConnectedExceptionTest {
+    @Test
+    void constructorWithNoArgsCreatesExpectedIggyNotConnectedException() {
+        var exception = new IggyNotConnectedException();
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualToIgnoringCase("Client not 
connected. Call connect() first.");
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void constructorWithMessageCreatesExpectedIggyNotConnectedException() {
+        var exception = new IggyNotConnectedException("message");
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyOperationNotSupportedExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyOperationNotSupportedExceptionTest.java
new file mode 100644
index 000000000..4af6219be
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyOperationNotSupportedExceptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyOperationNotSupportedExceptionTest {
+    @Test
+    void 
constructorWithOperationAndClientTypeCreatesExpectedIggyOperationNotSupportedException()
 {
+        var exception = new IggyOperationNotSupportedException("op", "TCP");
+
+        assertThat(exception.getMessage()).isEqualToIgnoringCase("Operation 
'op' is not supported by the TCP client");
+        assertThat(exception.getCause()).isNull();
+    }
+
+    @Test
+    void 
constructorWithMessageCreatesExpectedIggyOperationNotSupportedException() {
+        var exception = new IggyOperationNotSupportedException("message");
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
+    }
+
+    @Test
+    void getOperationReturnsOperation() {
+        var first = new IggyOperationNotSupportedException("op", "TCP");
+        var second = new IggyOperationNotSupportedException("message");
+
+        assertThat(first.getOperation()).isEqualTo("op");
+        assertThat(second.getOperation()).isNull();
+    }
+
+    @Test
+    void getClientTypeReturnsClientType() {
+        var first = new IggyOperationNotSupportedException("op", "TCP");
+        var second = new IggyOperationNotSupportedException("message");
+
+        assertThat(first.getClientType()).isEqualTo("TCP");
+        assertThat(second.getClientType()).isNull();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyProtocolExceptionTest.java
similarity index 52%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyProtocolExceptionTest.java
index 7335bfe78..cdade8aea 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyProtocolExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyProtocolExceptionTest {
+    @Test
+    void constructorWithMessageCreatesExpectedIggyProtocolException() {
+        var exception = new IggyProtocolException("message");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void constructorWithMessageAndCauseCreatesExpectedIggyProtocolException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyProtocolException("message", cause);
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyResourceNotFoundExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyResourceNotFoundExceptionTest.java
new file mode 100644
index 000000000..e64129177
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyResourceNotFoundExceptionTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyResourceNotFoundExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyResourceNotFoundException() {
+        var exception = new IggyResourceNotFoundException(
+                IggyErrorCode.RESOURCE_NOT_FOUND, 20, "resourceNotFound", 
Optional.of("foo"), Optional.of("bar"));
+
+        
assertThat(exception).isInstanceOf(IggyResourceNotFoundException.class);
+        
assertThat(exception.getErrorCode()).isEqualTo(IggyErrorCode.RESOURCE_NOT_FOUND);
+        assertThat(exception.getRawErrorCode()).isEqualTo(20);
+        assertThat(exception.getReason()).isEqualTo("resourceNotFound");
+        assertThat(exception.getField()).isEqualTo(Optional.of("foo"));
+        assertThat(exception.getErrorId()).isEqualTo(Optional.of("bar"));
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "RESOURCE_NOT_FOUND",
+                "CANNOT_LOAD_RESOURCE",
+                "STREAM_ID_NOT_FOUND",
+                "STREAM_NAME_NOT_FOUND",
+                "TOPIC_ID_NOT_FOUND",
+                "TOPIC_NAME_NOT_FOUND",
+                "PARTITION_NOT_FOUND",
+                "SEGMENT_NOT_FOUND",
+                "CLIENT_NOT_FOUND",
+                "CONSUMER_GROUP_ID_NOT_FOUND",
+                "CONSUMER_GROUP_NAME_NOT_FOUND",
+                "CONSUMER_GROUP_NOT_JOINED",
+                "MESSAGE_NOT_FOUND"
+            })
+    void matchesReturnsTrueForResourceNotFoundRelatedCodes(IggyErrorCode code) 
{
+        assertThat(IggyResourceNotFoundException.matches(code)).isTrue();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "RESOURCE_NOT_FOUND",
+                "CANNOT_LOAD_RESOURCE",
+                "STREAM_ID_NOT_FOUND",
+                "STREAM_NAME_NOT_FOUND",
+                "TOPIC_ID_NOT_FOUND",
+                "TOPIC_NAME_NOT_FOUND",
+                "PARTITION_NOT_FOUND",
+                "SEGMENT_NOT_FOUND",
+                "CLIENT_NOT_FOUND",
+                "CONSUMER_GROUP_ID_NOT_FOUND",
+                "CONSUMER_GROUP_NAME_NOT_FOUND",
+                "CONSUMER_GROUP_NOT_JOINED",
+                "MESSAGE_NOT_FOUND"
+            },
+            mode = Mode.EXCLUDE)
+    void matchesReturnsFalseForNonResourceNotFoundRelatedCodes(IggyErrorCode 
code) {
+        assertThat(IggyResourceNotFoundException.matches(code)).isFalse();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyServerExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyServerExceptionTest.java
index bff91cb14..cbfb55efe 100644
--- 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyServerExceptionTest.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyServerExceptionTest.java
@@ -21,8 +21,12 @@ package org.apache.iggy.exception;
 
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -153,4 +157,200 @@ class IggyServerExceptionTest {
             assertThat(exception.getMessage()).isEqualTo("Server error 
[code=40 (UNAUTHENTICATED)] [errorId: abc-123]");
         }
     }
+
+    @Test
+    void getErrorCodeReturnsExpectedErrorCode() {
+        var first = new IggyServerException(
+                IggyErrorCode.UNAUTHORIZED, 41, "reason", Optional.of("foo"), 
Optional.of("bar"));
+        var second = new IggyServerException(41);
+
+        assertThat(first.getErrorCode()).isEqualTo(IggyErrorCode.UNAUTHORIZED);
+        
assertThat(second.getErrorCode()).isEqualTo(IggyErrorCode.UNAUTHORIZED);
+    }
+
+    @Test
+    void getRawErrorCodeReturnsExpectedRawErrorCode() {
+        var first = new IggyServerException(
+                IggyErrorCode.UNAUTHENTICATED, 40, "reason", 
Optional.of("foo"), Optional.of("bar"));
+        var second = new IggyServerException(40);
+
+        assertThat(first.getRawErrorCode()).isEqualTo(40);
+        assertThat(second.getRawErrorCode()).isEqualTo(40);
+    }
+
+    @Test
+    void getReasonReturnsExpectedReason() {
+        var first = new IggyServerException(IggyErrorCode.ERROR, 1, "reason", 
Optional.of("foo"), Optional.of("bar"));
+        var second = new IggyServerException(1);
+
+        assertThat(first.getReason()).isEqualTo("reason");
+        assertThat(second.getReason()).isEqualTo("");
+    }
+
+    @Test
+    void getFieldReturnsExpectedField() {
+        var first = new IggyServerException(IggyErrorCode.ERROR, 1, "reason", 
Optional.of("foo"), Optional.of("bar"));
+        var second = new IggyServerException(1);
+
+        assertThat(first.getField()).isEqualTo(Optional.of("foo"));
+        assertThat(second.getField()).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    void getErrorIdReturnsExpectedField() {
+        var first = new IggyServerException(IggyErrorCode.ERROR, 1, "reason", 
Optional.of("foo"), Optional.of("bar"));
+        var second = new IggyServerException(1);
+
+        assertThat(first.getErrorId()).isEqualTo(Optional.of("bar"));
+        assertThat(second.getErrorId()).isEqualTo(Optional.empty());
+    }
+
+    public static Stream<Arguments> fromTcpResponseArgumentProvider() {
+        Optional<String> empty = Optional.empty();
+
+        return Stream.of(
+                Arguments.of(
+                        1,
+                        null,
+                        IggyServerException.class,
+                        new IggyServerException(IggyErrorCode.ERROR, 1, 
"Server error", empty, empty)),
+                Arguments.of(
+                        20,
+                        new byte[] {102, 111, 111},
+                        IggyResourceNotFoundException.class,
+                        new 
IggyResourceNotFoundException(IggyErrorCode.RESOURCE_NOT_FOUND, 20, "foo", 
empty, empty)),
+                Arguments.of(
+                        40,
+                        new byte[] {102, 111, 111},
+                        IggyAuthenticationException.class,
+                        new 
IggyAuthenticationException(IggyErrorCode.UNAUTHENTICATED, 40, "foo", empty, 
empty)),
+                Arguments.of(
+                        41,
+                        new byte[] {102, 111, 111},
+                        IggyAuthorizationException.class,
+                        new 
IggyAuthorizationException(IggyErrorCode.UNAUTHORIZED, 41, "foo", empty, 
empty)),
+                Arguments.of(
+                        46,
+                        new byte[] {102, 111, 111},
+                        IggyConflictException.class,
+                        new 
IggyConflictException(IggyErrorCode.USER_ALREADY_EXISTS, 46, "foo", empty, 
empty)),
+                Arguments.of(
+                        3,
+                        new byte[] {102, 111, 111},
+                        IggyValidationException.class,
+                        new 
IggyValidationException(IggyErrorCode.INVALID_COMMAND, 3, "foo", empty, empty)),
+                Arguments.of(
+                        12345678,
+                        new byte[] {102, 111, 111},
+                        IggyServerException.class,
+                        new IggyServerException(IggyErrorCode.UNKNOWN, 
12345678, "foo", empty, empty)));
+    }
+
+    @ParameterizedTest
+    @MethodSource("fromTcpResponseArgumentProvider")
+    <T extends IggyServerException> void 
fromTcpResponseReturnsExpectedException(
+            long status, byte[] payload, Class<T> expectedExceptionClass, T 
expectedException) {
+        var exception = IggyServerException.fromTcpResponse(status, payload);
+
+        assertThat(exception).isInstanceOf(expectedExceptionClass);
+        
assertThat(exception.getRawErrorCode()).isEqualTo(expectedException.getRawErrorCode());
+        
assertThat(exception.getErrorCode()).isEqualTo(expectedException.getErrorCode());
+        
assertThat(exception.getReason()).isEqualTo(expectedException.getReason());
+        
assertThat(exception.getField()).isEqualTo(expectedException.getField());
+        
assertThat(exception.getErrorId()).isEqualTo(expectedException.getErrorId());
+        
assertThat(exception.getMessage()).isEqualTo(expectedException.getMessage());
+    }
+
+    public static Stream<Arguments> fromHttpResponseArgumentProvider() {
+        return Stream.of(
+                Arguments.of(
+                        "id",
+                        "1",
+                        "error",
+                        "fld",
+                        IggyServerException.class,
+                        new IggyServerException(
+                                IggyErrorCode.ERROR, 1, "error", 
Optional.of("fld"), Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "20",
+                        "resourceNotFound",
+                        "fld",
+                        IggyResourceNotFoundException.class,
+                        new IggyResourceNotFoundException(
+                                IggyErrorCode.RESOURCE_NOT_FOUND,
+                                20,
+                                "resourceNotFound",
+                                Optional.of("fld"),
+                                Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "40",
+                        "unauthenticated",
+                        "fld",
+                        IggyAuthenticationException.class,
+                        new IggyAuthenticationException(
+                                IggyErrorCode.UNAUTHENTICATED,
+                                40,
+                                "unauthenticated",
+                                Optional.of("fld"),
+                                Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "41",
+                        "unauthorized",
+                        "fld",
+                        IggyAuthorizationException.class,
+                        new IggyAuthorizationException(
+                                IggyErrorCode.UNAUTHORIZED, 41, 
"unauthorized", Optional.of("fld"), Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "46",
+                        "userAlreadyExists",
+                        "fld",
+                        IggyConflictException.class,
+                        new IggyConflictException(
+                                IggyErrorCode.USER_ALREADY_EXISTS,
+                                46,
+                                "userAlreadyExists",
+                                Optional.of("fld"),
+                                Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "3",
+                        "invalidCommand",
+                        "fld",
+                        IggyValidationException.class,
+                        new IggyValidationException(
+                                IggyErrorCode.INVALID_COMMAND,
+                                3,
+                                "invalidCommand",
+                                Optional.of("fld"),
+                                Optional.of("id"))),
+                Arguments.of(
+                        "id",
+                        "123456789",
+                        "unknown",
+                        "fld",
+                        IggyServerException.class,
+                        new IggyServerException(
+                                IggyErrorCode.UNKNOWN, 123456789, "unknown", 
Optional.of("fld"), Optional.of("id"))));
+
+        //
+    }
+
+    @ParameterizedTest
+    @MethodSource("fromHttpResponseArgumentProvider")
+    <T extends IggyServerException> void 
fromHttpResponseReturnsExpectedException(
+            String id, String code, String reason, String field, Class<T> 
expectedExceptionClass, T expectedException) {
+        var exception = IggyServerException.fromHttpResponse(id, code, reason, 
field);
+
+        assertThat(exception).isInstanceOf(expectedExceptionClass);
+        
assertThat(exception.getRawErrorCode()).isEqualTo(expectedException.getRawErrorCode());
+        
assertThat(exception.getErrorCode()).isEqualTo(expectedException.getErrorCode());
+        
assertThat(exception.getReason()).isEqualTo(expectedException.getReason());
+        
assertThat(exception.getField()).isEqualTo(expectedException.getField());
+        
assertThat(exception.getErrorId()).isEqualTo(expectedException.getErrorId());
+        
assertThat(exception.getMessage()).isEqualTo(expectedException.getMessage());
+    }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTimeoutExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTimeoutExceptionTest.java
new file mode 100644
index 000000000..68529a405
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTimeoutExceptionTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyTimeoutExceptionTest {
+    @Test
+    void constructorWithNoArgsCreatesExpectedIggyTimeoutException() {
+        var exception = new IggyTimeoutException();
+
+        assertThat(exception.getMessage()).isEqualToIgnoringCase("Operation 
timed out");
+    }
+
+    @Test
+    void constructorWithMessageCreatesExpectedIggyTimeoutException() {
+        var exception = new IggyTimeoutException("message");
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
+    }
+
+    @Test
+    void constructorWithMessageAndCauseCreatesExpectedIggyTimeoutException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyTimeoutException("message", cause);
+
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTlsExceptionTest.java
similarity index 53%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTlsExceptionTest.java
index 7335bfe78..5fbcddc3a 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyTlsExceptionTest.java
@@ -17,30 +17,27 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.exception;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThat;
 
-    private final Integer code;
+class IggyTlsExceptionTest {
+    @Test
+    void constructorWithMessageCreatesExpectedIggyTlsException() {
+        var exception = new IggyTlsException("message");
 
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isNull();
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
+    @Test
+    void constructorWithMessageAndCauseCreatesExpectedIggyTlsException() {
+        var cause = new RuntimeException("cause");
+        var exception = new IggyTlsException("message", cause);
 
-    public Integer asCode() {
-        return code;
+        assertThat(exception.getMessage()).isEqualTo("message");
+        assertThat(exception.getCause()).isSameAs(cause);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyValidationExceptionTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyValidationExceptionTest.java
new file mode 100644
index 000000000..c7f9877eb
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/exception/IggyValidationExceptionTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.exception;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IggyValidationExceptionTest {
+    @Test
+    void constructorCreatesExpectedIggyValidationException() {
+        var exception = new IggyValidationException(
+                IggyErrorCode.INVALID_COMMAND, 3, "invalidCommand", 
Optional.of("foo"), Optional.of("bar"));
+
+        assertThat(exception).isInstanceOf(IggyValidationException.class);
+        
assertThat(exception.getErrorCode()).isEqualTo(IggyErrorCode.INVALID_COMMAND);
+        assertThat(exception.getRawErrorCode()).isEqualTo(3);
+        assertThat(exception.getReason()).isEqualTo("invalidCommand");
+        assertThat(exception.getField()).isEqualTo(Optional.of("foo"));
+        assertThat(exception.getErrorId()).isEqualTo(Optional.of("bar"));
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "INVALID_COMMAND",
+                "INVALID_FORMAT",
+                "FEATURE_UNAVAILABLE",
+                "CANNOT_PARSE_INT",
+                "CANNOT_PARSE_SLICE",
+                "CANNOT_PARSE_UTF8",
+                "INVALID_STREAM_NAME",
+                "CANNOT_CREATE_STREAM_DIRECTORY",
+                "INVALID_TOPIC_NAME",
+                "INVALID_REPLICATION_FACTOR",
+                "CANNOT_CREATE_TOPIC_DIRECTORY",
+                "CONSUMER_GROUP_MEMBER_NOT_FOUND",
+                "INVALID_CONSUMER_GROUP_NAME",
+                "TOO_MANY_MESSAGES",
+                "EMPTY_MESSAGES",
+                "TOO_BIG_MESSAGE",
+                "INVALID_MESSAGE_CHECKSUM"
+            })
+    void matchesReturnsTrueForValidationRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyValidationException.matches(code)).isTrue();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = IggyErrorCode.class,
+            names = {
+                "INVALID_COMMAND",
+                "INVALID_FORMAT",
+                "FEATURE_UNAVAILABLE",
+                "CANNOT_PARSE_INT",
+                "CANNOT_PARSE_SLICE",
+                "CANNOT_PARSE_UTF8",
+                "INVALID_STREAM_NAME",
+                "CANNOT_CREATE_STREAM_DIRECTORY",
+                "INVALID_TOPIC_NAME",
+                "INVALID_REPLICATION_FACTOR",
+                "CANNOT_CREATE_TOPIC_DIRECTORY",
+                "CONSUMER_GROUP_MEMBER_NOT_FOUND",
+                "INVALID_CONSUMER_GROUP_NAME",
+                "TOO_MANY_MESSAGES",
+                "EMPTY_MESSAGES",
+                "TOO_BIG_MESSAGE",
+                "INVALID_MESSAGE_CHECKSUM"
+            },
+            mode = Mode.EXCLUDE)
+    void matchesReturnsFalseForNonValidationRelatedCodes(IggyErrorCode code) {
+        assertThat(IggyValidationException.matches(code)).isFalse();
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/BaseIdentifierTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/BaseIdentifierTest.java
new file mode 100644
index 000000000..3b6520f4b
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/BaseIdentifierTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.identifier;
+
+import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+abstract class BaseIdentifierTest<T extends Identifier> {
+    protected abstract T ofName(String name);
+
+    protected abstract T ofId(Long id);
+
+    @Test
+    void ofNameReturnsIdentifierWithName() {
+        var identifier = ofName("name");
+
+        assertThat(identifier.getName()).isEqualTo("name");
+        assertThat(identifier.getId()).isNull();
+    }
+
+    @Test
+    void ofNameThrowsIggyInvalidArgumentExceptionWhenGivenNull() {
+        assertThatThrownBy(() -> 
ofName(null)).isInstanceOf(IggyInvalidArgumentException.class);
+    }
+
+    @Test
+    void ofIdReturnsIdentifierWithId() {
+        var identifier = ofId(123L);
+
+        assertThat(identifier.getId()).isEqualTo(123L);
+        assertThat(identifier.getName()).isNull();
+    }
+
+    @Test
+    void ofIdThrowsIggyInvalidArgumentExceptionWhenGivenNull() {
+        assertThatThrownBy(() -> 
ofId(null)).isInstanceOf(IggyInvalidArgumentException.class);
+    }
+
+    @Test
+    void toStringReturnsNameWhenIdentifierCreatedWithName() {
+        var identifier = ofName("name");
+
+        assertThat(identifier.toString()).isEqualTo("name");
+    }
+
+    @Test
+    void toStringReturnsIdWhenIdentifierCreatedWithId() {
+        var identifier = ofId(123L);
+
+        assertThat(identifier.toString()).isEqualTo("123");
+    }
+
+    @Test
+    void getKindReturns1WhenIdentifierCreatedWithId() {
+        var identifier = ofId(123L);
+
+        assertThat(identifier.getKind()).isEqualTo(1);
+    }
+
+    @Test
+    void getKindReturns2WhenIdentifierCreatedWithName() {
+        var identifier = ofName("name");
+
+        assertThat(identifier.getKind()).isEqualTo(2);
+    }
+
+    @Test
+    void getIdReturnsIdWhenIdentifierCreatedWithId() {
+        var identifier = ofId(123L);
+
+        assertThat(identifier.getId()).isEqualTo(123L);
+    }
+
+    @Test
+    void getIdReturnsNullWhenIdentifierCreatedWithName() {
+        var identifier = ofName("name");
+
+        assertThat(identifier.getId()).isNull();
+    }
+
+    @Test
+    void getNameReturnsNameWhenIdentifierCreatedWithName() {
+        var identifier = ofName("name");
+
+        assertThat(identifier.getName()).isEqualTo("name");
+    }
+
+    @Test
+    void getNameReturnsNullWhenIdentifierCreatedWithId() {
+        var identifier = ofId(123L);
+
+        assertThat(identifier.getName()).isNull();
+    }
+
+    @Test
+    void getSizeReturns6WhenIdentifierCreatedWithId() {
+        var identifier = ofId(123456789L);
+
+        assertThat(identifier.getSize()).isEqualTo(6);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"foo, 5", "a, 3", "abcdefghijklmnopqrstuvwxyz, 28"})
+    void getSizeReturnsExpectedSizeWhenIdentifierCreatedWithName(String name, 
int expectedSize) {
+        var identifier = ofName(name);
+
+        assertThat(identifier.getSize()).isEqualTo(expectedSize);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/ConsumerIdTest.java
similarity index 56%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/ConsumerIdTest.java
index 7335bfe78..2581064ca 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/ConsumerIdTest.java
@@ -17,30 +17,17 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.identifier;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+class ConsumerIdTest extends BaseIdentifierTest<ConsumerId> {
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
-
-    private final Integer code;
-
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
-    }
-
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
+    @Override
+    protected ConsumerId ofName(String name) {
+        return ConsumerId.of(name);
     }
 
-    public Integer asCode() {
-        return code;
+    @Override
+    protected ConsumerId ofId(Long id) {
+        return ConsumerId.of(id);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/IdentifierTest.java
similarity index 60%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/IdentifierTest.java
index 7335bfe78..cc7bee7a9 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/IdentifierTest.java
@@ -17,30 +17,23 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.identifier;
 
 import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-    private final Integer code;
-
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
+public class IdentifierTest {
+    @Test
+    void 
constructorThrowsIggyInvalidArgumentExceptionWhenBothNameAndIdAreProvided() {
+        assertThatThrownBy(() -> new FakeIdentifier("foo", 
123L)).isInstanceOf(IggyInvalidArgumentException.class);
     }
 
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
+    static class FakeIdentifier extends Identifier {
+        protected FakeIdentifier(@Nullable String name, @Nullable Long id) {
+            super(name, id);
         }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
-    }
-
-    public Integer asCode() {
-        return code;
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/StreamIdTest.java
similarity index 56%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/StreamIdTest.java
index 7335bfe78..a98e36151 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/StreamIdTest.java
@@ -17,30 +17,17 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.identifier;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+public class StreamIdTest extends BaseIdentifierTest<StreamId> {
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
-
-    private final Integer code;
-
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
-    }
-
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
+    @Override
+    protected StreamId ofName(String name) {
+        return StreamId.of(name);
     }
 
-    public Integer asCode() {
-        return code;
+    @Override
+    protected StreamId ofId(Long id) {
+        return StreamId.of(id);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/TopicIdTest.java
similarity index 56%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/TopicIdTest.java
index 7335bfe78..727b6d01c 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/TopicIdTest.java
@@ -17,30 +17,17 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.identifier;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+public class TopicIdTest extends BaseIdentifierTest<TopicId> {
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
-
-    private final Integer code;
-
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
-    }
-
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
+    @Override
+    protected TopicId ofName(String name) {
+        return TopicId.of(name);
     }
 
-    public Integer asCode() {
-        return code;
+    @Override
+    protected TopicId ofId(Long id) {
+        return TopicId.of(id);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/UserIdTest.java
similarity index 56%
copy from 
foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
copy to 
foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/UserIdTest.java
index 7335bfe78..2e43cbe04 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/topic/CompressionAlgorithm.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/identifier/UserIdTest.java
@@ -17,30 +17,17 @@
  * under the License.
  */
 
-package org.apache.iggy.topic;
+package org.apache.iggy.identifier;
 
-import org.apache.iggy.exception.IggyInvalidArgumentException;
+class UserIdTest extends BaseIdentifierTest<UserId> {
 
-public enum CompressionAlgorithm {
-    None(1),
-    Gzip(2);
-
-    private final Integer code;
-
-    CompressionAlgorithm(Integer code) {
-        this.code = code;
-    }
-
-    public static CompressionAlgorithm fromCode(byte code) {
-        for (CompressionAlgorithm algorithm : values()) {
-            if (algorithm.code == code) {
-                return algorithm;
-            }
-        }
-        throw new IggyInvalidArgumentException("Unknown compression algorithm 
code: " + code);
+    @Override
+    protected UserId ofName(String name) {
+        return UserId.of(name);
     }
 
-    public Integer asCode() {
-        return code;
+    @Override
+    protected UserId ofId(Long id) {
+        return UserId.of(id);
     }
 }
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java
index 22ce77a75..6d8dfa8c4 100644
--- 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java
@@ -34,6 +34,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.HexFormat;
 
 import static org.apache.iggy.serde.BytesDeserializer.readClientInfo;
+import static org.apache.iggy.serde.BytesDeserializer.readClientInfoDetails;
 import static org.apache.iggy.serde.BytesDeserializer.readConsumerGroup;
 import static org.apache.iggy.serde.BytesDeserializer.readConsumerGroupDetails;
 import static org.apache.iggy.serde.BytesDeserializer.readConsumerGroupInfo;
@@ -487,6 +488,31 @@ class BytesDeserializerTest {
             assertThat(clientInfo.transport()).isEqualTo("Tcp");
         }
 
+        @Test
+        void shouldDeserializeClientInfoDetails() {
+            var buffer = Unpooled.buffer();
+            buffer.writeIntLE(100); // client ID
+            buffer.writeIntLE(5); // user ID
+            buffer.writeByte(2); // transport (Quic)
+            buffer.writeIntLE(9); // address length
+            buffer.writeBytes("127.0.0.1".getBytes());
+            buffer.writeIntLE(1); // consumer groups count
+            buffer.writeIntLE(1); // first consumer group stream ID
+            buffer.writeIntLE(2); // first consumer group topic ID
+            buffer.writeIntLE(3); // first consumer group's consumer group ID
+
+            var clientInfo = readClientInfoDetails(buffer);
+
+            assertThat(clientInfo.clientId()).isEqualTo(100L);
+            assertThat(clientInfo.userId()).isPresent().hasValue(5L);
+            assertThat(clientInfo.address()).isEqualTo("127.0.0.1");
+            assertThat(clientInfo.transport()).isEqualTo("Quic");
+            assertThat(clientInfo.consumerGroups()).hasSize(1);
+            
assertThat(clientInfo.consumerGroups().get(0).streamId()).isEqualTo(1L);
+            
assertThat(clientInfo.consumerGroups().get(0).topicId()).isEqualTo(2L);
+            
assertThat(clientInfo.consumerGroups().get(0).consumerGroupId()).isEqualTo(3L);
+        }
+
         @Test
         void shouldDeserializeConsumerGroupInfo() {
             // given
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/stream/StreamDetailsTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/stream/StreamDetailsTest.java
new file mode 100644
index 000000000..a0e1adf0d
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/stream/StreamDetailsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.stream;
+
+import org.apache.iggy.topic.CompressionAlgorithm;
+import org.apache.iggy.topic.Topic;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class StreamDetailsTest {
+    @Test
+    void constructorWithStreamBaseCreatesExpectedStreamDetails() {
+        var base = new StreamBase(10L, BigInteger.valueOf(500L), "name", 
"size", BigInteger.ZERO, 1L);
+        var topics = List.of(new Topic(
+                1L,
+                BigInteger.ZERO,
+                "name",
+                "size",
+                BigInteger.TEN,
+                CompressionAlgorithm.None,
+                BigInteger.ONE,
+                (short) 2,
+                BigInteger.ZERO,
+                2L));
+
+        var streamDetails = new StreamDetails(base, topics);
+
+        assertThat(streamDetails.id()).isEqualTo(10L);
+        
assertThat(streamDetails.createdAt()).isEqualTo(BigInteger.valueOf(500L));
+        assertThat(streamDetails.name()).isEqualTo("name");
+        assertThat(streamDetails.size()).isEqualTo("size");
+        assertThat(streamDetails.messagesCount()).isEqualTo(BigInteger.ZERO);
+        assertThat(streamDetails.topicsCount()).isEqualTo(1L);
+        assertThat(streamDetails.topics()).isEqualTo(topics);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/system/ClientInfoDetailsTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/system/ClientInfoDetailsTest.java
new file mode 100644
index 000000000..94b58f8f1
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/system/ClientInfoDetailsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.system;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ClientInfoDetailsTest {
+    @Test
+    void constructorWithClientInfoCreatesExpectedClientInfoDetails() {
+        var clientInfo = new ClientInfo(321L, Optional.of(100L), "address", 
"transport", 1L);
+        var consumerGroupInfo = List.of(new ConsumerGroupInfo(1L, 2L, 3L));
+
+        var clientInfoDetails = new ClientInfoDetails(clientInfo, 
consumerGroupInfo);
+
+        assertThat(clientInfoDetails.clientId()).isEqualTo(321L);
+        assertThat(clientInfoDetails.userId()).isEqualTo(Optional.of(100L));
+        assertThat(clientInfoDetails.address()).isEqualTo("address");
+        assertThat(clientInfoDetails.transport()).isEqualTo("transport");
+        assertThat(clientInfoDetails.consumerGroupsCount()).isEqualTo(1L);
+        
assertThat(clientInfoDetails.consumerGroups()).isEqualTo(consumerGroupInfo);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/CompressionAlgorithmTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/CompressionAlgorithmTest.java
new file mode 100644
index 000000000..922e95423
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/CompressionAlgorithmTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.topic;
+
+import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class CompressionAlgorithmTest {
+
+    @ParameterizedTest
+    @CsvSource({"1, None", "2, Gzip"})
+    void fromCodeReturnsCorrectCompressionAlgorithmForValidCode(
+            int code, CompressionAlgorithm expectedCompressionAlgorithm) {
+        
assertThat(CompressionAlgorithm.fromCode(code)).isEqualTo(expectedCompressionAlgorithm);
+    }
+
+    @Test
+    void fromCodeThrowsIggyInvalidArgumentExceptionForInvalidCode() {
+        assertThatThrownBy(() -> CompressionAlgorithm.fromCode((byte) 127))
+                .isInstanceOf(IggyInvalidArgumentException.class);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"None, 1", "Gzip, 2"})
+    void asCodeReturnsValue(CompressionAlgorithm compressionAlgorithm, int 
expectedCode) {
+        assertThat(compressionAlgorithm.asCode()).isEqualTo(expectedCode);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/TopicDetailsTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/TopicDetailsTest.java
new file mode 100644
index 000000000..b605f9010
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/topic/TopicDetailsTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.topic;
+
+import org.apache.iggy.partition.Partition;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TopicDetailsTest {
+    @Test
+    void constructorWithTopicCreatesTopicDetailsWithExpectedValues() {
+        var topic = new Topic(
+                123L,
+                BigInteger.TEN,
+                "foo",
+                "size",
+                BigInteger.valueOf(10000L),
+                CompressionAlgorithm.Gzip,
+                BigInteger.TWO,
+                (short) 12,
+                BigInteger.ZERO,
+                1L);
+        var partitions = List.of(new Partition(1L, BigInteger.TEN, 2L, 
BigInteger.ZERO, "size", BigInteger.ONE));
+
+        var topicDetails = new TopicDetails(topic, partitions);
+
+        assertThat(topicDetails.id()).isEqualTo(123L);
+        assertThat(topicDetails.createdAt()).isEqualTo(BigInteger.TEN);
+        assertThat(topicDetails.name()).isEqualTo("foo");
+        assertThat(topicDetails.size()).isEqualTo("size");
+        
assertThat(topicDetails.messageExpiry()).isEqualTo(BigInteger.valueOf(10000L));
+        
assertThat(topicDetails.compressionAlgorithm()).isEqualTo(CompressionAlgorithm.Gzip);
+        assertThat(topicDetails.maxTopicSize()).isEqualTo(BigInteger.TWO);
+        assertThat(topicDetails.replicationFactor()).isEqualTo((short) 12);
+        assertThat(topicDetails.messagesCount()).isEqualTo(BigInteger.ZERO);
+        assertThat(topicDetails.partitionsCount()).isEqualTo(1L);
+        assertThat(topicDetails.partitions()).isEqualTo(partitions);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserInfoDetailsTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserInfoDetailsTest.java
new file mode 100644
index 000000000..3d4350dad
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserInfoDetailsTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.user;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UserInfoDetailsTest {
+    @Test
+    void constructorWithUserInfoCreatesUserInfoDetailsWithExpectedValues() {
+        var userInfo = new UserInfo(123L, BigInteger.TEN, UserStatus.Active, 
"foo");
+        var globalPermissions =
+                new GlobalPermissions(true, false, false, false, false, false, 
false, false, false, false);
+        var permissions = Optional.of(new Permissions(globalPermissions, 
Map.of()));
+        var userInfoDetails = new UserInfoDetails(userInfo, permissions);
+
+        assertThat(userInfoDetails.id()).isEqualTo(123L);
+        assertThat(userInfoDetails.createdAt()).isEqualTo(BigInteger.TEN);
+        assertThat(userInfoDetails.status()).isEqualTo(UserStatus.Active);
+        assertThat(userInfoDetails.username()).isEqualTo("foo");
+        assertThat(userInfoDetails.permissions()).isEqualTo(permissions);
+    }
+}
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserStatusTest.java 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserStatusTest.java
new file mode 100644
index 000000000..ba0045b37
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/user/UserStatusTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.user;
+
+import org.apache.iggy.exception.IggyInvalidArgumentException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class UserStatusTest {
+    @ParameterizedTest
+    @CsvSource({"1, Active", "2, Inactive"})
+    void fromCodeReturnsCorrectUserStatusForValidCode(int code, UserStatus 
expectedUserStatus) {
+        assertThat(UserStatus.fromCode(code)).isEqualTo(expectedUserStatus);
+    }
+
+    @Test
+    void fromCodeThrowsIggyInvalidArgumentExceptionForInvalidCode() {
+        assertThatThrownBy(() -> 
UserStatus.fromCode(100)).isInstanceOf(IggyInvalidArgumentException.class);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"Active, 1", "Inactive, 2"})
+    void asCodeReturnsValue(UserStatus userStatus, int expectedCode) {
+        assertThat(userStatus.asCode()).isEqualTo(expectedCode);
+    }
+}

Reply via email to