From a05df692c856279cb5071bf4ff1b48bdf9229117 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Fri, 18 Oct 2024 18:21:34 +0200
Subject: [PATCH] OZG-6891 Transform runtime exceptions to checked exceptions

---
 .../de/ozgcloud/xta/client/XtaClient.java     |  27 ++-
 .../ozgcloud/xta/client/XtaClientFactory.java |   8 +-
 .../xta/client/config/XtaConfigValidator.java |   6 +-
 .../client/core/WrappedXtaServiceFactory.java |   4 +-
 .../xta/client/core/XtaClientService.java     |  20 +-
 .../client/core/XtaClientServiceFactory.java  |   4 +-
 .../xta/client/core/XtaExceptionHandler.java  |  67 +++++++
 .../core/XtaExceptionHandlerFactory.java      |  14 ++
 .../xta/client/core/XtaPortTripleFactory.java |  12 +-
 .../core/XtaTLSClientParametersFactory.java   |   8 +-
 ...Exception.java => XtaClientException.java} |  67 +++----
 ... => XtaClientInitializationException.java} |  67 ++++---
 .../xdomea/XdomeaMetaDataValidator.java       |  18 +-
 .../xdomea/XdomeaXtaMessageCreator.java       |  10 +-
 .../xdomea/reader/XdomeaValueReader.java      |   8 +-
 .../ozgcloud/xta/client/XtaClientITCase.java  |  51 ++++-
 .../xta/client/XtaClientRemoteITCase.java     |   2 +
 .../de/ozgcloud/xta/client/XtaClientTest.java |  86 ++++++++-
 .../client/config/XtaConfigValidatorTest.java |  18 +-
 .../core/XtaExceptionHandlerFactoryTest.java  |  29 +++
 .../client/core/XtaExceptionHandlerTest.java  | 176 ++++++++++++++++++
 .../XtaTLSClientParametersFactoryTest.java    |   4 +-
 .../ClientRuntimeExceptionTestFactory.java    |  33 ++++
 .../xdomea/XdomeaMetaDataValidatorTest.java   |   8 +-
 .../xdomea/XdomeaXtaMessageCreatorITCase.java |   6 +-
 .../xdomea/XdomeaXtaMessageCreatorTest.java   |   4 +-
 .../xdomea/reader/XdomeaValueReaderTest.java  |   4 +-
 27 files changed, 614 insertions(+), 147 deletions(-)
 create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandler.java
 create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactory.java
 rename src/main/java/de/ozgcloud/xta/client/exception/{ClientException.java => XtaClientException.java} (76%)
 rename src/main/java/de/ozgcloud/xta/client/exception/{ClientInitializationException.java => XtaClientInitializationException.java} (71%)
 create mode 100644 src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactoryTest.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerTest.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/factory/ClientRuntimeExceptionTestFactory.java

diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
index b11bac9..3a25a8f 100644
--- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java
+++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
@@ -10,8 +10,10 @@ import jakarta.validation.constraints.NotNull;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
 import de.ozgcloud.xta.client.core.XtaClientService;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.core.XtaExceptionHandler;
 import de.ozgcloud.xta.client.exception.ClientRuntimeException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import de.ozgcloud.xta.client.model.XtaIdentifier;
 import de.ozgcloud.xta.client.model.XtaMessage;
 import de.ozgcloud.xta.client.model.XtaMessageMetaData;
@@ -31,14 +33,23 @@ public class XtaClient {
 	private final XtaClientService service;
 	private final XtaClientConfig config;
 	private final FetchMessageParameterFactory fetchMessageParameterFactory;
+	private final XtaExceptionHandler exceptionHandler;
 
 	static final String NO_MESSAGE_CLOSED_WARNING = "No message has been closed although more are pending. Try increasing max list items.";
 
-	public static XtaClient from(XtaClientConfig config) throws ClientInitializationException {
+	public static XtaClient from(XtaClientConfig config) throws XtaClientInitializationException {
 		return XtaClientFactory.from(config).create();
 	}
 
-	public List<XtaTransportReport> fetchMessages(@NotNull Consumer<XtaMessage> processMessage) {
+	public List<XtaTransportReport> fetchMessages(@NotNull Consumer<XtaMessage> processMessage) throws XtaClientException {
+		try {
+			return fetchMessagesRaw(processMessage);
+		} catch (RuntimeException exception) {
+			throw exceptionHandler.deriveXtaClientException(exception);
+		}
+	}
+
+	List<XtaTransportReport> fetchMessagesRaw(Consumer<XtaMessage> processMessage) {
 		return config.getClientIdentifiers().stream()
 				.filter(service::checkAccountActive)
 				.map(clientIdentifier -> fetchMessageParameterFactory.create(clientIdentifier, processMessage))
@@ -125,7 +136,15 @@ public class XtaClient {
 		log.error("Processing of message '%s' failed! Not closing message.".formatted(messageId), exception);
 	}
 
-	public XtaTransportReport sendMessage(@Valid XtaMessage messageWithoutMessageId) {
+	public XtaTransportReport sendMessage(@Valid XtaMessage message) throws XtaClientException {
+		try {
+			return sendMessageRaw(message);
+		} catch (RuntimeException exception) {
+			throw exceptionHandler.deriveXtaClientException(exception);
+		}
+	}
+
+	XtaTransportReport sendMessageRaw(@Valid XtaMessage messageWithoutMessageId) {
 		var metaData = messageWithoutMessageId.metaData();
 		throwExceptionIfAccountInactive(metaData.authorIdentifier());
 		throwExceptionIfServiceNotAvailable(metaData);
diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
index 1a38191..81d0fa5 100644
--- a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
+++ b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
@@ -3,7 +3,8 @@ package de.ozgcloud.xta.client;
 import de.ozgcloud.xta.client.config.XtaClientConfig;
 import de.ozgcloud.xta.client.config.XtaConfigValidator;
 import de.ozgcloud.xta.client.core.XtaClientServiceFactory;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.core.XtaExceptionHandlerFactory;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import lombok.Builder;
 import lombok.RequiredArgsConstructor;
 
@@ -15,6 +16,7 @@ public class XtaClientFactory {
 	private final XtaClientServiceFactory xtaClientServiceFactory;
 	private final XtaClientConfig config;
 	private final FetchMessageParameterFactory fetchMessageParameterFactory;
+	private final XtaExceptionHandlerFactory exceptionHandlerFactory;
 
 	public static XtaClientFactory from(final XtaClientConfig config) {
 		return XtaClientFactory.builder()
@@ -22,15 +24,17 @@ public class XtaClientFactory {
 				.configValidator(XtaConfigValidator.builder().build())
 				.xtaClientServiceFactory(XtaClientServiceFactory.from(config))
 				.fetchMessageParameterFactory(FetchMessageParameterFactory.builder().build())
+				.exceptionHandlerFactory(XtaExceptionHandlerFactory.builder().build())
 				.build();
 	}
 
-	public XtaClient create() throws ClientInitializationException {
+	public XtaClient create() throws XtaClientInitializationException {
 		configValidator.validate(config);
 		return XtaClient.builder()
 				.config(config)
 				.service(xtaClientServiceFactory.create())
 				.fetchMessageParameterFactory(fetchMessageParameterFactory)
+				.exceptionHandler(exceptionHandlerFactory.create())
 				.build();
 	}
 }
diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java b/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
index 9582164..f0fd961 100644
--- a/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
+++ b/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
@@ -7,7 +7,7 @@ import java.util.stream.Collectors;
 import jakarta.validation.Validation;
 import jakarta.validation.Validator;
 
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import lombok.Builder;
 import lombok.RequiredArgsConstructor;
 
@@ -22,10 +22,10 @@ public class XtaConfigValidator {
 		}
 	}
 
-	public void validate(final XtaClientConfig config) throws ClientInitializationException {
+	public void validate(final XtaClientConfig config) throws XtaClientInitializationException {
 		var violations = VALIDATOR.validate(config);
 		if (!violations.isEmpty()) {
-			throw new ClientInitializationException("Client configuration is invalid:\n" + violations.stream()
+			throw new XtaClientInitializationException("Client configuration is invalid:\n" + violations.stream()
 					.map(v -> "'%s' %s".formatted(v.getPropertyPath().toString(), v.getMessage()))
 					.collect(Collectors.joining("\n")));
 		}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/WrappedXtaServiceFactory.java b/src/main/java/de/ozgcloud/xta/client/core/WrappedXtaServiceFactory.java
index 6a909b5..9d89e96 100644
--- a/src/main/java/de/ozgcloud/xta/client/core/WrappedXtaServiceFactory.java
+++ b/src/main/java/de/ozgcloud/xta/client/core/WrappedXtaServiceFactory.java
@@ -3,7 +3,7 @@ package de.ozgcloud.xta.client.core;
 import org.mapstruct.factory.Mappers;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import de.ozgcloud.xta.client.mapper.RequestMapper;
 import de.ozgcloud.xta.client.mapper.ResponseMapper;
 import lombok.Builder;
@@ -25,7 +25,7 @@ public class WrappedXtaServiceFactory {
 				.build();
 	}
 
-	public WrappedXtaService create() throws ClientInitializationException {
+	public WrappedXtaService create() throws XtaClientInitializationException {
 		return WrappedXtaService.builder()
 				.ports(xtaPortTripleFactory.create())
 				.requestMapper(requestMapper)
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java b/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java
index 3c1aacc..6dc98c8 100644
--- a/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java
@@ -28,11 +28,13 @@ public class XtaClientService {
 	private final WrappedXtaService service;
 	private final XtaClientConfig config;
 
+	static final String TRANSPORT_REPORT_FAILED_ERROR = "Failed to get transport report!";
+
 	public Optional<XtaTransportReport> getTransportReport(XtaMessageMetaData messageMetaData) {
 		try {
 			return Optional.of(getTransportReportOrThrowException(messageMetaData));
 		} catch (ClientRuntimeException e) {
-			logError("Failed to get transport report!", e);
+			logError(TRANSPORT_REPORT_FAILED_ERROR, e);
 			return Optional.empty();
 		}
 	}
@@ -42,7 +44,7 @@ public class XtaClientService {
 		var readerClientIdentifier = message.metaData().readerIdentifier();
 		try {
 			service.close(messageId, readerClientIdentifier);
-		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) {
+		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException | RuntimeException e) {
 			logError("Failed to close message! '%s' (reader: %s)".formatted(messageId, readerClientIdentifier), e);
 		}
 	}
@@ -52,7 +54,7 @@ public class XtaClientService {
 		var readerClientIdentifier = messageMetaData.readerIdentifier();
 		try {
 			return Optional.of(service.getMessage(messageId, readerClientIdentifier));
-		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) {
+		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException | RuntimeException e) {
 			logError("Failed to get message by id ! '%s' (reader: %s)".formatted(messageId, readerClientIdentifier.value()), e);
 			return Optional.empty();
 		}
@@ -61,7 +63,7 @@ public class XtaClientService {
 	public Optional<XtaMessageMetaDataListing> getStatusList(XtaIdentifier clientIdentifier) {
 		try {
 			return Optional.of(service.getStatusList(clientIdentifier, config.getMaxListItems()));
-		} catch (PermissionDeniedException | XTAWSTechnicalProblemException e) {
+		} catch (PermissionDeniedException | XTAWSTechnicalProblemException | RuntimeException e) {
 			logError("Failed to get status list!", e);
 			return Optional.empty();
 		}
@@ -75,7 +77,7 @@ public class XtaClientService {
 		try {
 			service.checkAccountActive(clientIdentifier);
 			return true;
-		} catch (XTAWSTechnicalProblemException e) {
+		} catch (XTAWSTechnicalProblemException | RuntimeException e) {
 			throw new ClientRuntimeException("Failed to check account active!", e);
 		} catch (PermissionDeniedException e) {
 			return false;
@@ -88,7 +90,7 @@ public class XtaClientService {
 			service.sendMessage(message);
 			return getTransportReportOrThrowException(message.metaData());
 		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | SyncAsyncException | ParameterIsNotValidException |
-				MessageVirusDetectionException | MessageSchemaViolationException e) {
+				MessageVirusDetectionException | MessageSchemaViolationException | RuntimeException e) {
 			throw new ClientRuntimeException("Failed to send message!", e);
 		}
 	}
@@ -98,7 +100,7 @@ public class XtaClientService {
 		try {
 			var messageId = service.createMessageId(authorIdentifier);
 			return createXtaMessageWithMessageId(messageWithoutMessageId, messageId);
-		} catch (XTAWSTechnicalProblemException | PermissionDeniedException e) {
+		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | RuntimeException e) {
 			throw new ClientRuntimeException("Failed to create message id!", e);
 		}
 	}
@@ -116,7 +118,7 @@ public class XtaClientService {
 		var authorId = messageMetaData.authorIdentifier();
 		try {
 			return service.getTransportReport(messageId, authorId);
-		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) {
+		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException | RuntimeException e) {
 			throw new ClientRuntimeException(
 					"Failed to get transport report! (messageId: %s, reader: %s)".formatted(messageId, authorId.value()), e);
 		}
@@ -129,7 +131,7 @@ public class XtaClientService {
 					messageMetaData.readerIdentifier(),
 					messageMetaData.authorIdentifier()
 			);
-		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | ParameterIsNotValidException exception) {
+		} catch (XTAWSTechnicalProblemException | PermissionDeniedException | ParameterIsNotValidException | RuntimeException exception) {
 			logWarning("Failed to lookup service for message '%s'!".formatted(messageMetaData.messageId()), exception);
 			return false;
 		}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java
index 61a95de..0343c50 100644
--- a/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java
@@ -1,7 +1,7 @@
 package de.ozgcloud.xta.client.core;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import lombok.Builder;
 
 @Builder
@@ -17,7 +17,7 @@ public class XtaClientServiceFactory {
 				.build();
 	}
 
-	public XtaClientService create() throws ClientInitializationException {
+	public XtaClientService create() throws XtaClientInitializationException {
 		return XtaClientService.builder()
 				.config(config)
 				.service(wrappedXtaServiceFactory.create())
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandler.java b/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandler.java
new file mode 100644
index 0000000..1fe3570
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandler.java
@@ -0,0 +1,67 @@
+package de.ozgcloud.xta.client.core;
+
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import de.ozgcloud.xta.client.exception.ClientRuntimeException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
+import genv3.de.xoev.transport.xta.x211.ExceptionType;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+
+@Builder
+@RequiredArgsConstructor
+public class XtaExceptionHandler {
+
+	static final String UNEXPECTED_ERROR_MESSAGE = "An unexpected error occurred. Please report this to the xta-client maintainers.";
+
+	public XtaClientException deriveXtaClientException(RuntimeException exception) {
+		if (exception instanceof ClientRuntimeException) {
+			return deriveXtaClientExceptionFromClientRuntimeException((ClientRuntimeException) exception);
+		}
+		return new XtaClientException(UNEXPECTED_ERROR_MESSAGE, exception);
+	}
+
+	XtaClientException deriveXtaClientExceptionFromClientRuntimeException(ClientRuntimeException exception) {
+		var cause = exception.getCause();
+		var detailMessageLines = getDetailLines(cause);
+		var message = Stream.concat(
+				Stream.of(exception.getMessage()),
+				detailMessageLines
+		).collect(Collectors.joining("\n"));
+		return new XtaClientException(message, cause);
+	}
+
+	Stream<String> getDetailLines(Throwable cause) {
+		return Optional.ofNullable(cause)
+				.filter(Exception.class::isInstance)
+				.map(Exception.class::cast)
+				.map(this::deriveDetailLinesFromException)
+				.orElse(Stream.empty());
+	}
+
+	Stream<String> deriveDetailLinesFromException(Exception exception) {
+		return getExceptionType(exception)
+				.map(ExceptionType::getErrorCode)
+				.map(num -> Stream.of(exception.getMessage(), "[%s] %s".formatted(num.getCode(), num.getName())))
+				.orElse(Stream.empty());
+	}
+
+	private Optional<ExceptionType> getExceptionType(Exception exception) {
+		return getPrivateFieldValue(exception, "faultInfo")
+				.filter(ExceptionType.class::isInstance)
+				.map(ExceptionType.class::cast);
+	}
+
+	private Optional<Object> getPrivateFieldValue(Object object, String fieldName) {
+		try {
+			var field = object.getClass().getDeclaredField(fieldName);
+			field.setAccessible(true);
+			return Optional.of(field.get(object));
+		} catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) {
+			return Optional.empty();
+		}
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactory.java
new file mode 100644
index 0000000..67cb8a8
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactory.java
@@ -0,0 +1,14 @@
+package de.ozgcloud.xta.client.core;
+
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+
+@Builder
+@RequiredArgsConstructor
+public class XtaExceptionHandlerFactory {
+
+	public XtaExceptionHandler create() {
+		return XtaExceptionHandler.builder()
+				.build();
+	}
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaPortTripleFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XtaPortTripleFactory.java
index 3da0a62..908dc1e 100644
--- a/src/main/java/de/ozgcloud/xta/client/core/XtaPortTripleFactory.java
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaPortTripleFactory.java
@@ -14,7 +14,7 @@ import org.apache.cxf.message.Message;
 import org.apache.cxf.transport.http.HTTPConduit;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import genv3.de.xoev.transport.xta.x211.XTAService;
 import lombok.Builder;
 import lombok.RequiredArgsConstructor;
@@ -41,7 +41,7 @@ public class XtaPortTripleFactory {
 				.build();
 	}
 
-	public XtaPortTriple create() throws ClientInitializationException {
+	public XtaPortTriple create() throws XtaClientInitializationException {
 		log.debug("[createXtaService] Using config: {}", config);
 		return new XtaPortTriple(
 				configurePort(config.getManagementServiceUrl(), xtaService.getManagementPort()),
@@ -50,13 +50,13 @@ public class XtaPortTripleFactory {
 		);
 	}
 
-	private <T> T configurePort(final String endpointUrl, final T port) throws ClientInitializationException {
+	private <T> T configurePort(final String endpointUrl, final T port) throws XtaClientInitializationException {
 		var bindingProvider = (BindingProvider) port;
 		configureBinding(endpointUrl, bindingProvider);
 		return port;
 	}
 
-	void configureBinding(final String endpointUrl, final BindingProvider port) throws ClientInitializationException {
+	void configureBinding(final String endpointUrl, final BindingProvider port) throws XtaClientInitializationException {
 		configureRequestContext(endpointUrl, port.getRequestContext());
 		configureClient(getClientFromPort(port));
 	}
@@ -74,7 +74,7 @@ public class XtaPortTripleFactory {
 		));
 	}
 
-	void configureClient(Client client) throws ClientInitializationException {
+	void configureClient(Client client) throws XtaClientInitializationException {
 		if (config.isLogSoapRequests()) {
 			configureOutInterceptors(client.getOutInterceptors());
 		}
@@ -105,7 +105,7 @@ public class XtaPortTripleFactory {
 		return inInterceptor;
 	}
 
-	void configureHttpConduit(HTTPConduit conduit) throws ClientInitializationException {
+	void configureHttpConduit(HTTPConduit conduit) throws XtaClientInitializationException {
 		conduit.setTlsClientParameters(tlsClientParametersFactory.create());
 	}
 }
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java
index fee35b4..f314f4f 100644
--- a/src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java
@@ -15,7 +15,7 @@ import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.hc.core5.ssl.SSLContextBuilder;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import lombok.Builder;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -29,7 +29,7 @@ public class XtaTLSClientParametersFactory {
 
 	private final XtaClientConfig config;
 
-	public TLSClientParameters create() throws ClientInitializationException {
+	public TLSClientParameters create() throws XtaClientInitializationException {
 		log.debug("[createXtaTLsParameters] Using config: {}", config);
 		var sslContext = createXtaSslContext();
 
@@ -38,7 +38,7 @@ public class XtaTLSClientParametersFactory {
 		return parameters;
 	}
 
-	public SSLContext createXtaSslContext() throws ClientInitializationException {
+	public SSLContext createXtaSslContext() throws XtaClientInitializationException {
 		try {
 			var sslContextBuilder = createSSLContextBuilder();
 			var clientCertKeystore = config.getClientCertKeystore();
@@ -52,7 +52,7 @@ public class XtaTLSClientParametersFactory {
 			return sslContextBuilder.build();
 		} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException | UnrecoverableKeyException | IOException |
 				CertificateException e) {
-			throw new ClientInitializationException("Failed to create SSL context: " + e.getMessage(), e);
+			throw new XtaClientInitializationException("Failed to create SSL context: " + e.getMessage(), e);
 		}
 	}
 
diff --git a/src/main/java/de/ozgcloud/xta/client/exception/ClientException.java b/src/main/java/de/ozgcloud/xta/client/exception/XtaClientException.java
similarity index 76%
rename from src/main/java/de/ozgcloud/xta/client/exception/ClientException.java
rename to src/main/java/de/ozgcloud/xta/client/exception/XtaClientException.java
index 0b89833..e00a6b1 100644
--- a/src/main/java/de/ozgcloud/xta/client/exception/ClientException.java
+++ b/src/main/java/de/ozgcloud/xta/client/exception/XtaClientException.java
@@ -1,33 +1,34 @@
-/**
- *     XTA-Client Java Library
- *     Copyright (C) 2021 Koordinierungsstelle für IT-Standards (KoSIT)
- *
- *     This program is free software: you can redistribute it and/or modify
- *     it under the terms of the GNU General Public License as published by
- *     the Free Software Foundation, either version 3 of the License, or
- *     (at your option) any later version.
- *
- *     This program is distributed in the hope that it will be useful,
- *     but WITHOUT ANY WARRANTY; without even the implied warranty of
- *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *     GNU General Public License for more details.
- *
- *     You should have received a copy of the GNU General Public License
- *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-package de.ozgcloud.xta.client.exception;
-
-
-/**
- * Allgemeiner Fehler zum signalisieren von clientseitigen Problemen.
- */
-public class ClientException extends Exception {
-
-  public ClientException(final String message, final Throwable cause) {
-    super(message, cause);
-  }
-
-  public ClientException(final String message) {
-    super(message);
-  }
-}
+/**
+ *     XTA-Client Java Library
+ *     Copyright (C) 2021 Koordinierungsstelle für IT-Standards (KoSIT)
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package de.ozgcloud.xta.client.exception;
+
+
+/**
+ * A generic exception during xta client method execution.
+ *
+ */
+public class XtaClientException extends Exception {
+
+  public XtaClientException(final String message, final Throwable cause) {
+    super(message, cause);
+  }
+
+  public XtaClientException(final String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/exception/ClientInitializationException.java b/src/main/java/de/ozgcloud/xta/client/exception/XtaClientInitializationException.java
similarity index 71%
rename from src/main/java/de/ozgcloud/xta/client/exception/ClientInitializationException.java
rename to src/main/java/de/ozgcloud/xta/client/exception/XtaClientInitializationException.java
index 5a1b2a7..534c435 100644
--- a/src/main/java/de/ozgcloud/xta/client/exception/ClientInitializationException.java
+++ b/src/main/java/de/ozgcloud/xta/client/exception/XtaClientInitializationException.java
@@ -1,34 +1,33 @@
-/**
- *     XTA-Client Java Library
- *     Copyright (C) 2021 Koordinierungsstelle für IT-Standards (KoSIT)
- *
- *     This program is free software: you can redistribute it and/or modify
- *     it under the terms of the GNU General Public License as published by
- *     the Free Software Foundation, either version 3 of the License, or
- *     (at your option) any later version.
- *
- *     This program is distributed in the hope that it will be useful,
- *     but WITHOUT ANY WARRANTY; without even the implied warranty of
- *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *     GNU General Public License for more details.
- *
- *     You should have received a copy of the GNU General Public License
- *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-package de.ozgcloud.xta.client.exception;
-
-/**
- * Spezieller Fehler zum signalisieren von clientseitigen Problemen während der Initialisierung.
- *
- * @author hmaterny
- */
-public class ClientInitializationException extends ClientException {
-
-  public ClientInitializationException(final String message, final Throwable cause) {
-    super(message, cause);
-  }
-
-  public ClientInitializationException(final String message) {
-    super(message);
-  }
-}
+/**
+ *     XTA-Client Java Library
+ *     Copyright (C) 2021 Koordinierungsstelle für IT-Standards (KoSIT)
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package de.ozgcloud.xta.client.exception;
+
+/**
+ * A generic exception during xta client initialization.
+ *
+ */
+public class XtaClientInitializationException extends XtaClientException {
+
+  public XtaClientInitializationException(final String message, final Throwable cause) {
+    super(message, cause);
+  }
+
+  public XtaClientInitializationException(final String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidator.java b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidator.java
index eafbad0..7a5c363 100644
--- a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidator.java
+++ b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidator.java
@@ -5,7 +5,7 @@ import static de.ozgcloud.xta.client.xdomea.mapper.MetadataMapper.*;
 import java.util.List;
 import java.util.Set;
 
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.model.XtaFile;
 import de.ozgcloud.xta.client.model.XtaMessageMetaData;
 import de.ozgcloud.xta.client.xdomea.reader.XdomeaXmlValues;
@@ -26,7 +26,7 @@ public class XdomeaMetaDataValidator {
 			XtaFile xdomeaZipFile,
 			XdomeaXmlValues xdomeaXmlValues,
 			XtaMessageMetaData messageMetadataData
-	) throws ClientException {
+	) throws XtaClientException {
 		validateIdentifierPrefixes(
 				xdomeaXmlValues.authorIdPrefix(),
 				xdomeaXmlValues.readerIdPrefix()
@@ -39,14 +39,14 @@ public class XdomeaMetaDataValidator {
 		validatePrimaryDocumentReferences(xdomeaZipFile, xdomeaXmlValues.primaryDocumentNames());
 	}
 
-	void validateIdentifierPrefixes(String authorIdPrefix, String readerIdPrefix) throws ClientException {
+	void validateIdentifierPrefixes(String authorIdPrefix, String readerIdPrefix) throws XtaClientException {
 		validateIdentifierPrefix(authorIdPrefix, AUTHOR_ID_PREFIX, AUTHOR_ID_PREFIX_NAME);
 		validateIdentifierPrefix(readerIdPrefix, READER_ID_PREFIX, READER_ID_PREFIX_NAME);
 	}
 
-	void validateIdentifierPrefix(String prefix, String expectedPrefix, String prefixName) throws ClientException {
+	void validateIdentifierPrefix(String prefix, String expectedPrefix, String prefixName) throws XtaClientException {
 		if (!expectedPrefix.equals(normalizePrefix(prefix))) {
-			throw new ClientException("Expect prefix of %s identifier to be '%s'! (actual: %s)".formatted(prefixName, expectedPrefix, prefix));
+			throw new XtaClientException("Expect prefix of %s identifier to be '%s'! (actual: %s)".formatted(prefixName, expectedPrefix, prefix));
 		}
 	}
 
@@ -54,19 +54,19 @@ public class XdomeaMetaDataValidator {
 		return prefix.endsWith(":") ? prefix.substring(0, prefix.length() - 1) : prefix;
 	}
 
-	void validatePrimaryDocumentReferences(XtaFile xdomeaZipFile, List<String> primaryDocumentNames) throws ClientException {
+	void validatePrimaryDocumentReferences(XtaFile xdomeaZipFile, List<String> primaryDocumentNames) throws XtaClientException {
 		var entryNames = Set.copyOf(zipFileEntryReader.getEntryNames(xdomeaZipFile.content()));
 		for (var primaryDocumentName : primaryDocumentNames) {
 			if (!entryNames.contains(primaryDocumentName)) {
-				throw new ClientException("Primary document reference '%s' not found in xdomea zip file!".formatted(primaryDocumentName));
+				throw new XtaClientException("Primary document reference '%s' not found in xdomea zip file!".formatted(primaryDocumentName));
 			}
 		}
 	}
 
-	void validateZipFileName(String xdomeaZipFileName, String processId, String messageTypeCode) throws ClientException {
+	void validateZipFileName(String xdomeaZipFileName, String processId, String messageTypeCode) throws XtaClientException {
 		var expectedXdomeaZipFileName = "%s_%s.zip".formatted(processId, messageTypeCode);
 		if (!expectedXdomeaZipFileName.equals(xdomeaZipFileName)) {
-			throw new ClientException(
+			throw new XtaClientException(
 					"Expect xdomea zip file name to equal '%s'! (actual: '%s')".formatted(expectedXdomeaZipFileName, xdomeaZipFileName)
 			);
 		}
diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java
index 68ad3d9..05c1949 100644
--- a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java
+++ b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java
@@ -11,7 +11,7 @@ import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
 import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.model.XtaFile;
 import de.ozgcloud.xta.client.model.XtaMessage;
 import de.ozgcloud.xta.client.model.XtaMessageMetaData;
@@ -36,7 +36,7 @@ public class XdomeaXtaMessageCreator {
 		return XdomeaXtaMessageCreatorFactory.createInstance().create();
 	}
 
-	public XtaMessage createMessage(XtaFile xdomeaZipFile) throws ClientException {
+	public XtaMessage createMessage(XtaFile xdomeaZipFile) throws XtaClientException {
 		return XtaMessage.builder()
 				.metaData(deriveValidMetaData(xdomeaZipFile))
 				.messageFile(xdomeaZipFile)
@@ -44,18 +44,18 @@ public class XdomeaXtaMessageCreator {
 				.build();
 	}
 
-	XtaMessageMetaData deriveValidMetaData(XtaFile xdomeaZipFile) throws ClientException {
+	XtaMessageMetaData deriveValidMetaData(XtaFile xdomeaZipFile) throws XtaClientException {
 		try {
 			var xdomeaXmlValues = readXdomeaXmlValues(xdomeaZipFile);
 			var messageMetadataData = metadataMapper.mapXtaMessageMetadata(xdomeaXmlValues);
 			metaDataValidator.validate(xdomeaZipFile, xdomeaXmlValues, messageMetadataData);
 			return messageMetadataData;
 		} catch (TechnicalException e) {
-			throw new ClientException("Failed to derive valid message metadata from xdomea document!", e);
+			throw new XtaClientException("Failed to derive valid message metadata from xdomea document!", e);
 		}
 	}
 
-	XdomeaXmlValues readXdomeaXmlValues(XtaFile xdomeaZipFile) throws ClientException {
+	XdomeaXmlValues readXdomeaXmlValues(XtaFile xdomeaZipFile) throws XtaClientException {
 		var document = readXdomeaXmlDocument(xdomeaZipFile);
 		return xdomeaValueReader.readValues(document);
 	}
diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReader.java b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReader.java
index 90dcbeb..52f3a87 100644
--- a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReader.java
+++ b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReader.java
@@ -7,7 +7,7 @@ import java.util.stream.Collectors;
 
 import org.w3c.dom.Document;
 
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import lombok.Builder;
 import lombok.RequiredArgsConstructor;
 
@@ -34,7 +34,7 @@ public class XdomeaValueReader {
 
 	private final Map<String, XmlValueReader> xmlValueReaders;
 
-	public XdomeaXmlValues readValues(Document xdomeaXmlDocument) throws ClientException {
+	public XdomeaXmlValues readValues(Document xdomeaXmlDocument) throws XtaClientException {
 		return XdomeaXmlValues.builder()
 				.processId(readRequiredValue(xdomeaXmlDocument, PROCESS_ID_XPATH))
 				.messageTypeCode(readRequiredValue(xdomeaXmlDocument, MESSAGE_TYPE_ID_SUFFIX_XPATH))
@@ -52,11 +52,11 @@ public class XdomeaValueReader {
 				.toList();
 	}
 
-	String readRequiredValue(Document xdomeaXmlDocument, String xpathString) throws ClientException {
+	String readRequiredValue(Document xdomeaXmlDocument, String xpathString) throws XtaClientException {
 		return getXmlValueReader(xpathString)
 				.readNonEmptyTexts(xdomeaXmlDocument)
 				.findFirst()
-				.orElseThrow(() -> new ClientException("Required value " + xpathString + " not found in xdomea xml document!"));
+				.orElseThrow(() -> new XtaClientException("Required value " + xpathString + " not found in xdomea xml document!"));
 	}
 
 	XmlValueReader getXmlValueReader(String xpath) {
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
index b28ff46..aedd99a 100644
--- a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
@@ -9,7 +9,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.stream.Stream;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -17,6 +16,7 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.extension.StaticStringListAppender;
 import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader;
 import de.ozgcloud.xta.client.extension.XtaTestServerSetupExtension;
@@ -61,7 +61,6 @@ class XtaClientITCase {
 		closeAllMessages(setupClient, READER_CLIENT_IDENTIFIER3);
 	}
 
-
 	@DisplayName("fetch messages")
 	@Nested
 	class TestFetchMessages {
@@ -93,6 +92,16 @@ class XtaClientITCase {
 							.build());
 		}
 
+		@DisplayName("should throw exception on connection failure")
+		@Test
+		@SneakyThrows
+		void shouldThrowExceptionOnConnectionFailure() {
+			setupClientWithoutTrustStore();
+
+			assertThatThrownBy(() -> testClient.fetchMessages((message) -> fail("Should not process any message!")))
+					.isInstanceOf(XtaClientException.class);
+		}
+
 		@DisplayName("should fetch no messages if no client identifier is configured")
 		@Test
 		void shouldFetchNoMessagesIfNoClientIdentifierIsConfigured() {
@@ -308,14 +317,21 @@ class XtaClientITCase {
 			var transportReports = fetchMessages();
 
 			if (hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)) {
+				// Case: sendMessages.get(3) was not fetched.
 				assertThat(supportCheckedMetadataItems).hasSize(1 + 2 + 2);
+				assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(4));
+				assertThatTransportReports(transportReports)
+						.reportExactlyFor(processedMessages)
+						.haveExactlyGreenStatusFor(messageIdBySendIndex(4));
 			} else {
+				// Case: sendMessages.get(3) was fetched.
 				assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2);
+				assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(3), sendMessages.get(4));
+				assertThatTransportReports(transportReports)
+						.reportExactlyFor(processedMessages)
+						.haveExactlyGreenStatusFor(messageIdBySendIndex(3), messageIdBySendIndex(4));
 			}
-			assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(3), sendMessages.get(4));
-			assertThatTransportReports(transportReports)
-					.reportExactlyFor(processedMessages)
-					.haveExactlyGreenStatusFor(messageIdBySendIndex(3), messageIdBySendIndex(4));
+
 		}
 
 		@DisplayName("should process messages only if supported, with support for author3")
@@ -352,6 +368,7 @@ class XtaClientITCase {
 			return sendMessageIds.get(sendIndex);
 		}
 
+		@SneakyThrows
 		private List<XtaTransportReport> fetchMessages() {
 			return testClient.fetchMessages((message) -> {
 				processedMessages.add(message);
@@ -376,6 +393,16 @@ class XtaClientITCase {
 			message = XtaMessageExampleLoader.load(messageConfig);
 		}
 
+		@DisplayName("should throw exception on connection failure")
+		@Test
+		@SneakyThrows
+		void shouldThrowExceptionOnConnectionFailure() {
+			setupClientWithoutTrustStore();
+
+			assertThatThrownBy(() -> testClient.sendMessage(message))
+					.isInstanceOf(XtaClientException.class);
+		}
+
 		@DisplayName("should return transport report with open status")
 		@Test
 		@SneakyThrows
@@ -385,5 +412,17 @@ class XtaClientITCase {
 			assertThat(transportReport.status()).isEqualTo(XtaMessageStatus.OPEN);
 		}
 	}
+
+	@SneakyThrows
+	private void setupClientWithoutTrustStore() {
+		testClient = XtaClient.from(
+				XTA_TEST_SERVER_SETUP_EXTENSION.createSpecificClientConfigBuilder()
+						.trustStore(null)
+						.clientIdentifiers(List.of(READER_CLIENT_IDENTIFIER1))
+						.maxListItems(TWO_MAX_LIST_ITEMS)
+						.isMessageSupported(metaData -> fail("Should not process any message!"))
+						.build()
+		);
+	}
 }
 
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java
index 1ab424d..72384e7 100644
--- a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java
@@ -230,10 +230,12 @@ class XtaClientRemoteITCase {
 			).toList();
 		}
 
+		@SneakyThrows
 		private List<XtaTransportReport> fetchDevMessages() {
 			return devClient.fetchMessages(this::processMessage);
 		}
 
+		@SneakyThrows
 		private List<XtaTransportReport> fetchTestMessages() {
 			return testClient.fetchMessages(this::processMessage);
 		}
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
index 37c7ef4..181f3db 100644
--- a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
@@ -26,7 +26,10 @@ import org.mockito.Spy;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
 import de.ozgcloud.xta.client.core.XtaClientService;
+import de.ozgcloud.xta.client.core.XtaExceptionHandler;
 import de.ozgcloud.xta.client.exception.ClientRuntimeException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
+import de.ozgcloud.xta.client.factory.ClientRuntimeExceptionTestFactory;
 import de.ozgcloud.xta.client.factory.XtaMessageMetaDataListingTestFactory;
 import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory;
 import de.ozgcloud.xta.client.factory.XtaMessageTestFactory;
@@ -49,6 +52,9 @@ class XtaClientTest {
 	@Mock
 	private FetchMessageParameterFactory fetchMessageParameterFactory;
 
+	@Mock
+	private XtaExceptionHandler exceptionHandler;
+
 	@Spy
 	@InjectMocks
 	private XtaClient client;
@@ -77,6 +83,43 @@ class XtaClientTest {
 		@Mock
 		private Consumer<XtaMessage> processMessage;
 
+		@Mock
+		private XtaTransportReport transportReport;
+
+		@Mock
+		private XtaClientException exception;
+
+		@DisplayName("should return")
+		@Test
+		@SneakyThrows
+		void shouldReturn() {
+			doReturn(List.of(transportReport)).when(client).fetchMessagesRaw(processMessage);
+
+			var result = client.fetchMessagesRaw(processMessage);
+
+			assertThat(result).containsExactly(transportReport);
+		}
+
+		@DisplayName("should throw checked exception on runtime exception")
+		@Test
+		void shouldThrowCheckedExceptionOnRuntimeException() {
+			var runTimeException = ClientRuntimeExceptionTestFactory.create();
+			doThrow(runTimeException).when(client).sendMessageRaw(message);
+			when(exceptionHandler.deriveXtaClientException(runTimeException)).thenReturn(exception);
+
+			assertThatThrownBy(() -> client.fetchMessagesRaw(processMessage))
+					.isEqualTo(exception);
+		}
+
+	}
+
+	@DisplayName("fetch messages raw")
+	@Nested
+	class TestFetchMessagesRaw {
+
+		@Mock
+		private Consumer<XtaMessage> processMessage;
+
 		@Mock
 		private FetchMessageParameter parameter;
 
@@ -99,7 +142,7 @@ class XtaClientTest {
 				when(fetchMessageParameterFactory.create(SELF_IDENTIFIER, processMessage)).thenReturn(parameter);
 				doReturn(Stream.of(transportReport)).when(client).fetchMessagesForClientIdentifier(parameter);
 
-				var result = client.fetchMessages(processMessage);
+				var result = fetchMessages();
 
 				assertThat(result).containsExactly(transportReport);
 			}
@@ -114,11 +157,16 @@ class XtaClientTest {
 			void shouldFetchNoMessages() {
 				doReturn(false).when(service).checkAccountActive(SELF_IDENTIFIER);
 
-				var result = client.fetchMessages(processMessage);
+				var result = fetchMessages();
 
 				assertThat(result).isEmpty();
 			}
 		}
+
+		@SneakyThrows
+		private List<XtaTransportReport> fetchMessages() {
+			return client.fetchMessages(processMessage);
+		}
 	}
 
 	@DisplayName("fetch messages for client identifier")
@@ -586,6 +634,39 @@ class XtaClientTest {
 		@Mock
 		private XtaTransportReport transportReport;
 
+		@Mock
+		private XtaClientException exception;
+
+		@DisplayName("should return")
+		@Test
+		@SneakyThrows
+		void shouldReturn() {
+			doReturn(transportReport).when(client).sendMessageRaw(message);
+
+			var result = client.sendMessage(message);
+
+			assertThat(result).isEqualTo(transportReport);
+		}
+
+		@DisplayName("should throw checked exception on runtime exception")
+		@Test
+		void shouldThrowCheckedExceptionOnRuntimeException() {
+			var runtimeException = ClientRuntimeExceptionTestFactory.create();
+			doThrow(runtimeException).when(client).sendMessageRaw(message);
+			when(exceptionHandler.deriveXtaClientException(runtimeException)).thenReturn(exception);
+
+			assertThatThrownBy(() -> client.sendMessage(message))
+					.isEqualTo(exception);
+		}
+	}
+
+	@DisplayName("send message raw")
+	@Nested
+	class TestSendMessageRaw {
+
+		@Mock
+		private XtaTransportReport transportReport;
+
 		@BeforeEach
 		void mock() {
 			doNothing().when(client).throwExceptionIfAccountInactive(any());
@@ -619,6 +700,7 @@ class XtaClientTest {
 			assertThat(result).isEqualTo(transportReport);
 		}
 
+		@SneakyThrows
 		private XtaTransportReport sendMessage() {
 			return client.sendMessage(message);
 		}
diff --git a/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
index 0f6625c..4e0a958 100644
--- a/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
@@ -10,7 +10,7 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory;
 import de.ozgcloud.xta.client.model.XtaIdentifier;
 import lombok.SneakyThrows;
@@ -52,7 +52,7 @@ class XtaConfigValidatorTest {
 					.build();
 
 			assertThatThrownBy(() -> validator.validate(config))
-					.isInstanceOf(ClientInitializationException.class)
+					.isInstanceOf(XtaClientInitializationException.class)
 					.hasMessageContaining("clientIdentifiers");
 		}
 
@@ -64,7 +64,7 @@ class XtaConfigValidatorTest {
 					.build();
 
 			assertThatThrownBy(() -> validator.validate(config))
-					.isInstanceOf(ClientInitializationException.class)
+					.isInstanceOf(XtaClientInitializationException.class)
 					.hasMessageContaining("managementServiceUrl");
 		}
 
@@ -76,7 +76,7 @@ class XtaConfigValidatorTest {
 					.build();
 
 			assertThatThrownBy(() -> validator.validate(config))
-					.isInstanceOf(ClientInitializationException.class)
+					.isInstanceOf(XtaClientInitializationException.class)
 					.hasMessageContaining("sendServiceUrl");
 		}
 
@@ -88,7 +88,7 @@ class XtaConfigValidatorTest {
 					.build();
 
 			assertThatThrownBy(() -> validator.validate(config))
-					.isInstanceOf(ClientInitializationException.class)
+					.isInstanceOf(XtaClientInitializationException.class)
 					.hasMessageContaining("msgBoxServiceUrl");
 		}
 
@@ -100,7 +100,7 @@ class XtaConfigValidatorTest {
 					.build();
 
 			assertThatThrownBy(() -> validator.validate(config))
-					.isInstanceOf(ClientInitializationException.class)
+					.isInstanceOf(XtaClientInitializationException.class)
 					.hasMessageContaining("maxListItems");
 		}
 
@@ -113,7 +113,7 @@ class XtaConfigValidatorTest {
 				var config = createKeystoreWithClientCertKeyStore(o -> o.password(null));
 
 				assertThatThrownBy(() -> validator.validate(config))
-						.isInstanceOf(ClientInitializationException.class)
+						.isInstanceOf(XtaClientInitializationException.class)
 						.hasMessageContaining("password");
 			}
 
@@ -123,7 +123,7 @@ class XtaConfigValidatorTest {
 				var config = createKeystoreWithClientCertKeyStore(o -> o.type(""));
 
 				assertThatThrownBy(() -> validator.validate(config))
-						.isInstanceOf(ClientInitializationException.class)
+						.isInstanceOf(XtaClientInitializationException.class)
 						.hasMessageContaining("type");
 			}
 
@@ -133,7 +133,7 @@ class XtaConfigValidatorTest {
 				var config = createKeystoreWithClientCertKeyStore(o -> o.content(null));
 
 				assertThatThrownBy(() -> validator.validate(config))
-						.isInstanceOf(ClientInitializationException.class)
+						.isInstanceOf(XtaClientInitializationException.class)
 						.hasMessageContaining("content");
 			}
 
diff --git a/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactoryTest.java
new file mode 100644
index 0000000..985c7aa
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerFactoryTest.java
@@ -0,0 +1,29 @@
+package de.ozgcloud.xta.client.core;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+class XtaExceptionHandlerFactoryTest {
+
+	@InjectMocks
+	@Spy
+	private XtaExceptionHandlerFactory exceptionHandlerFactory;
+
+	@DisplayName("create")
+	@Nested
+	class TestCreate {
+		@DisplayName("should return")
+		@Test
+		void shouldReturn() {
+			var result = exceptionHandlerFactory.create();
+
+			assertThat(result).isNotNull();
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerTest.java b/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerTest.java
new file mode 100644
index 0000000..df6b0a2
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/core/XtaExceptionHandlerTest.java
@@ -0,0 +1,176 @@
+package de.ozgcloud.xta.client.core;
+
+import static de.ozgcloud.xta.client.core.XtaExceptionHandler.*;
+import static de.ozgcloud.xta.client.factory.ClientRuntimeExceptionTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.xta.client.exception.ClientRuntimeException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
+import de.ozgcloud.xta.client.factory.ClientRuntimeExceptionTestFactory;
+import genv3.de.xoev.transport.xta.x211.PermissionDeniedException;
+
+class XtaExceptionHandlerTest {
+
+	@InjectMocks
+	@Spy
+	XtaExceptionHandler exceptionHandler;
+
+	private ClientRuntimeException exception;
+
+	@BeforeEach
+	void setUp() {
+		exception = ClientRuntimeExceptionTestFactory.create();
+	}
+
+	@DisplayName("derive xta client exception")
+	@Nested
+	class TestDeriveXtaClientException {
+
+		@Mock
+		private XtaClientException derivedClientException;
+
+		@DisplayName("should derive xta client exception from client runtime exception")
+		@Test
+		void shouldDeriveXtaClientExceptionFromClientRuntimeException() {
+			doReturn(derivedClientException).when(exceptionHandler).deriveXtaClientExceptionFromClientRuntimeException(exception);
+
+			var result = exceptionHandler.deriveXtaClientException(exception);
+
+			assertThat(result).isEqualTo(derivedClientException);
+		}
+
+		@DisplayName("without client runtime exception instance")
+		@Nested
+		class TestWithoutClientRuntimeExceptionInstance {
+
+			@Mock
+			private RuntimeException runtimeException;
+
+			@DisplayName("should return with unknown error message")
+			@Test
+			void shouldReturnWithUnknownErrorMessage() {
+				var result = exceptionHandler.deriveXtaClientException(runtimeException);
+
+				assertThat(result.getMessage()).isEqualTo(UNEXPECTED_ERROR_MESSAGE);
+			}
+
+			@DisplayName("should have cause")
+			@Test
+			void shouldHaveCause() {
+				var result = exceptionHandler.deriveXtaClientException(runtimeException);
+
+				assertThat(result.getCause()).isEqualTo(runtimeException);
+			}
+		}
+	}
+
+	@DisplayName("derive xta client exception from client runtime exception")
+	@Nested
+	class TestDeriveXtaClientExceptionFromClientRuntimeException {
+
+		@DisplayName("should keep message if no cause")
+		@Test
+		void shouldKeepMessageIfNoCause() {
+			var exception = new ClientRuntimeException(MESSAGE);
+
+			var result = exceptionHandler.deriveXtaClientExceptionFromClientRuntimeException(exception);
+
+			assertThat(result.getMessage()).isEqualTo(MESSAGE);
+		}
+
+		@DisplayName("should keep message if cause is not an xta exception")
+		@Test
+		void shouldKeepMessageIfCauseIsNotAnXtaException() {
+			var cause = new Exception();
+			var exception = new ClientRuntimeException(MESSAGE, cause);
+
+			var result = exceptionHandler.deriveXtaClientExceptionFromClientRuntimeException(exception);
+
+			assertThat(result.getMessage()).isEqualTo(MESSAGE);
+
+		}
+
+		@DisplayName("should extend detail message if cause is an xta exception")
+		@Test
+		void shouldExtendDetailMessageIfCauseIsAnXtaException() {
+			var result = exceptionHandler.deriveXtaClientExceptionFromClientRuntimeException(exception);
+
+			assertThat(result.getMessage()).contains(MESSAGE, CAUSE_MESSAGE, CAUSE_CODE, CAUSE_NAME);
+		}
+	}
+
+	@DisplayName("get detail lines")
+	@Nested
+	class TestGetDetailLines {
+
+		@DisplayName("should return empty stream if cause is null")
+		@Test
+		void shouldReturnEmptyStreamIfCauseIsNull() {
+			var result = exceptionHandler.getDetailLines(null).toList();
+
+			assertThat(result).isEmpty();
+		}
+
+		@DisplayName("should return empty stream if cause is not an exception")
+		@Test
+		void shouldReturnEmptyStreamIfCauseIsNotAnException() {
+			var result = exceptionHandler.getDetailLines(new Throwable()).toList();
+
+			assertThat(result).isEmpty();
+		}
+
+		@DisplayName("should return empty stream if cause is not an xta exception")
+		@Test
+		void shouldReturnEmptyStreamIfCauseIsNotAnXtaException() {
+			var result = exceptionHandler.getDetailLines(new Exception()).toList();
+
+			assertThat(result).isEmpty();
+		}
+
+		@DisplayName("should return detail lines if cause is an xta exception")
+		@Test
+		void shouldReturnDetailLinesIfCauseIsAnXtaException() {
+			var causeException = (PermissionDeniedException) exception.getCause();
+			doReturn(Stream.of(CAUSE_MESSAGE)).when(exceptionHandler).deriveDetailLinesFromException(causeException);
+
+			var result = exceptionHandler.getDetailLines(causeException).toList();
+
+			assertThat(result).containsExactly(CAUSE_MESSAGE);
+		}
+	}
+
+	@DisplayName("derive detail lines from exception")
+	@Nested
+	class TestDeriveDetailLinesFromException {
+
+		@DisplayName("should return empty stream if exception type is not present")
+		@Test
+		void shouldReturnEmptyStreamIfExceptionTypeIsNotPresent() {
+			var result = exceptionHandler.deriveDetailLinesFromException(new Exception()).toList();
+
+			assertThat(result).isEmpty();
+		}
+
+		@DisplayName("should return stream with error code and name if exception type is present")
+		@Test
+		void shouldReturnStreamWithErrorCodeAndNameIfExceptionTypeIsPresent() {
+			var causeException = (PermissionDeniedException) exception.getCause();
+
+			var result = exceptionHandler.deriveDetailLinesFromException(causeException).toList();
+
+			assertThat(result).containsExactly(CAUSE_MESSAGE, "[%s] %s".formatted(CAUSE_CODE, CAUSE_NAME));
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java
index f9077cf..8047c2d 100644
--- a/src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java
@@ -22,7 +22,7 @@ import org.mockito.Spy;
 
 import de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory;
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import de.ozgcloud.xta.client.exception.XtaClientInitializationException;
 import lombok.SneakyThrows;
 
 class XtaTLSClientParametersFactoryTest {
@@ -123,7 +123,7 @@ class XtaTLSClientParametersFactoryTest {
 				doThrow(new KeyStoreException("something")).when(sslContextBuilder).loadKeyMaterial(any(), any());
 
 				assertThatThrownBy(() -> factory.createXtaSslContext())
-						.isInstanceOf(ClientInitializationException.class);
+						.isInstanceOf(XtaClientInitializationException.class);
 			}
 		}
 
diff --git a/src/test/java/de/ozgcloud/xta/client/factory/ClientRuntimeExceptionTestFactory.java b/src/test/java/de/ozgcloud/xta/client/factory/ClientRuntimeExceptionTestFactory.java
new file mode 100644
index 0000000..bfa3c0f
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/factory/ClientRuntimeExceptionTestFactory.java
@@ -0,0 +1,33 @@
+package de.ozgcloud.xta.client.factory;
+
+import de.ozgcloud.xta.client.exception.ClientRuntimeException;
+import genv3.de.xoev.transport.xta.x211.CodeFehlernummer;
+import genv3.de.xoev.transport.xta.x211.PermissionDeniedException;
+import genv3.de.xoev.transport.xta.x211.PermissionDeniedExceptionType;
+
+public class ClientRuntimeExceptionTestFactory {
+
+	public static final String MESSAGE = "message";
+	public static final String CAUSE_MESSAGE = "cause message";
+	public static final String CAUSE_CODE = "cause code";
+	public static final String CAUSE_NAME = "cause name";
+
+	public static ClientRuntimeException create() {
+		return new ClientRuntimeException(MESSAGE, createPermissionDeniedException());
+	}
+
+	private static PermissionDeniedException createPermissionDeniedException() {
+		return new PermissionDeniedException(CAUSE_MESSAGE, createPermissionDeniedExceptionType());
+	}
+
+	private static PermissionDeniedExceptionType createPermissionDeniedExceptionType() {
+		var code = new CodeFehlernummer();
+		code.setCode(CAUSE_CODE);
+		code.setName(CAUSE_NAME);
+
+		var exceptionType = new PermissionDeniedExceptionType();
+		exceptionType.setErrorCode(code);
+		return exceptionType;
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidatorTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidatorTest.java
index b3251be..97dd894 100644
--- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidatorTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaMetaDataValidatorTest.java
@@ -21,7 +21,7 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.factory.XdomeaXmlValuesTestFactory;
 import de.ozgcloud.xta.client.factory.XtaFileTestFactory;
 import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory;
@@ -170,7 +170,7 @@ class XdomeaMetaDataValidatorTest {
 		})
 		void shouldThrowClientExceptionForWrongPrefix(String prefix) {
 			assertThatThrownBy(() -> validator.validateIdentifierPrefix(prefix, EXPECTED_PREFIX, PREFIX_NAME))
-					.isInstanceOf(ClientException.class)
+					.isInstanceOf(XtaClientException.class)
 					.hasMessageContaining(PREFIX_NAME);
 		}
 
@@ -200,7 +200,7 @@ class XdomeaMetaDataValidatorTest {
 		})
 		void shouldThrowClientExceptionForWrongZipFileName(String wrongZipFileName) {
 			assertThatThrownBy(() -> validator.validateZipFileName(wrongZipFileName, PROCESS_ID, MESSAGE_TYPE_CODE))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 
 	}
@@ -241,7 +241,7 @@ class XdomeaMetaDataValidatorTest {
 			when(zipFileEntryReader.getEntryNames(contentDataHandler)).thenReturn(INVALID_ZIP_ENTRY_NAMES);
 
 			assertThatThrownBy(() -> validator.validatePrimaryDocumentReferences(xdomeaZipFile, PRIMARY_DOCUMENT_NAMES))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 	}
 
diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java
index 6256c85..7ff3ffd 100644
--- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java
+++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java
@@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader;
 import de.ozgcloud.xta.client.model.XtaFile;
 import lombok.SneakyThrows;
@@ -77,7 +77,7 @@ class XdomeaXtaMessageCreatorITCase {
 			);
 
 			assertThatThrownBy(() -> creator.createMessage(invalidMessageZipFile))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 
 		@DisplayName("should not throw with valid message 0201")
@@ -118,7 +118,7 @@ class XdomeaXtaMessageCreatorITCase {
 			);
 
 			assertThatThrownBy(() -> creator.createMessage(invalidMessageZipFile))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 
 		@DisplayName("should not throw with valid message 0401")
diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java
index 6ffa37b..a183007 100644
--- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java
@@ -20,7 +20,7 @@ import org.mockito.Spy;
 import org.w3c.dom.Document;
 
 import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import de.ozgcloud.xta.client.factory.XdomeaXmlValuesTestFactory;
 import de.ozgcloud.xta.client.factory.XtaFileTestFactory;
 import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory;
@@ -156,7 +156,7 @@ class XdomeaXtaMessageCreatorTest {
 			doThrow(technicalException).when(metadataMapper).mapXtaMessageMetadata(xdomeaXmlValues);
 
 			assertThatThrownBy(() -> creator.deriveValidMetaData(xdomeaZipFile))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 
 	}
diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReaderTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReaderTest.java
index 9c72e7a..5b8fd71 100644
--- a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReaderTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XdomeaValueReaderTest.java
@@ -16,7 +16,7 @@ import org.mockito.Mock;
 import org.mockito.Spy;
 import org.w3c.dom.Document;
 
-import de.ozgcloud.xta.client.exception.ClientException;
+import de.ozgcloud.xta.client.exception.XtaClientException;
 import lombok.SneakyThrows;
 
 class XdomeaValueReaderTest {
@@ -183,7 +183,7 @@ class XdomeaValueReaderTest {
 			when(xmlValueReader.readNonEmptyTexts(xdomeaXmlDocument)).thenReturn(Stream.empty());
 
 			assertThatThrownBy(() -> xdomeaValueReader.readRequiredValue(xdomeaXmlDocument, XPATH_STRING))
-					.isInstanceOf(ClientException.class);
+					.isInstanceOf(XtaClientException.class);
 		}
 	}
 
-- 
GitLab