From f0b3d0cda2f0f726876f7fa47357a5481c4aebb7 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Mon, 22 Jul 2024 16:57:13 +0200
Subject: [PATCH] KOP-2139 KOP-2445 setup: Reduce and refactor code from
 xta-client-example-lib

---
 pom.xml                                       |  17 +
 .../de/ozgcloud/xta/client/XtaClient.java     |  28 +-
 .../ozgcloud/xta/client/XtaClientFactory.java |  32 ++
 .../xta/client/config/HttpClientPolicy.java   |  36 --
 .../ozgcloud/xta/client/config/HttpProxy.java |  36 --
 .../ozgcloud/xta/client/config/Keystore.java  |  35 --
 .../xta/client/config/XtaClientConfig.java    | 159 +++-----
 .../xta/client/config/XtaClientSettings.java  |  62 ---
 .../xta/client/config/XtaConfigValidator.java |  33 ++
 .../EmptyNamespaceMessageOutInterceptor.java  | 114 ------
 .../client/core/ManagementServiceClient.java  |  79 ----
 .../core/ManagementServiceClientBuilder.java  |  55 ---
 .../xta/client/core/MsgBoxServiceClient.java  |  86 ----
 .../core/MsgBoxServiceClientBuilder.java      |  54 ---
 .../xta/client/core/SendServiceClient.java    |  73 ----
 .../client/core/SendServiceClientBuilder.java |  55 ---
 .../xta/client/core/ServiceBuilder.java       | 176 --------
 .../ozgcloud/xta/client/core/VersionInfo.java |  61 ---
 .../de/ozgcloud/xta/client/core/WsClient.java | 266 ------------
 .../xta/client/core/WsClientConfig.java       |  49 ---
 .../xta/client/core/XTAServiceFactory.java    | 106 +++++
 .../core/XtaTLSClientParametersFactory.java   |  68 ++++
 .../ozgcloud/xta/client/model/Identifier.java | 130 ------
 .../de/ozgcloud/xta/client/util/FileUtil.java |  43 --
 .../xta/client/util/KeyStoreUtil.java         |  97 -----
 .../client/XtaClientConfigTestFactory.java    |  99 +++++
 .../xta/client/XtaClientFactoryTest.java      |  74 ++++
 .../de/ozgcloud/xta/client/XtaClientTest.java |  43 +-
 .../client/config/XtaConfigValidatorTest.java | 138 +++++++
 .../client/core/XTAServiceFactoryTest.java    | 377 ++++++++++++++++++
 .../XtaTLSClientParametersFactoryTest.java    | 192 +++++++++
 src/test/resources/junit-platform.properties  |   1 +
 .../org.junit.jupiter.api.extension.Extension |   1 +
 33 files changed, 1207 insertions(+), 1668 deletions(-)
 create mode 100644 src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/config/HttpClientPolicy.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/config/HttpProxy.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/config/Keystore.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/config/XtaClientSettings.java
 create mode 100644 src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/EmptyNamespaceMessageOutInterceptor.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClient.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClientBuilder.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClient.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClientBuilder.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/SendServiceClient.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/SendServiceClientBuilder.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/ServiceBuilder.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/VersionInfo.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/WsClient.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/core/WsClientConfig.java
 create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XTAServiceFactory.java
 create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/model/Identifier.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/util/FileUtil.java
 delete mode 100644 src/main/java/de/ozgcloud/xta/client/util/KeyStoreUtil.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/XtaClientConfigTestFactory.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/core/XTAServiceFactoryTest.java
 create mode 100644 src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java
 create mode 100644 src/test/resources/junit-platform.properties
 create mode 100644 src/test/resources/services/org.junit.jupiter.api.extension.Extension

diff --git a/pom.xml b/pom.xml
index ce70297..d46c664 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
 		<!-- build versions -->
 		<cxf.version>4.0.3</cxf.version>
 		<cxf-xjc.version>4.0.0</cxf-xjc.version>
+		<jsr305.version>3.0.2</jsr305.version>
 
 		<!-- Build settings -->
 		<timestamp>${maven.build.timestamp}</timestamp>
@@ -79,6 +80,12 @@
 			<version>${cxf.version}</version>
 		</dependency>
 
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>jsr305</artifactId>
+			<version>${jsr305.version}</version>
+		</dependency>
+
 		<!-- Test -->
 		<dependency>
 			<groupId>org.projectlombok</groupId>
@@ -90,6 +97,16 @@
 			<artifactId>mockito-core</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-junit-jupiter</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-engine</artifactId>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>org.assertj</groupId>
 			<artifactId>assertj-core</artifactId>
diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
index 214c195..d9cb840 100644
--- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java
+++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
@@ -3,28 +3,22 @@ package de.ozgcloud.xta.client;
 import org.apache.commons.lang3.NotImplementedException;
 
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.core.MsgBoxServiceClient;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
 import de.ozgcloud.xta.client.model.XtaMessage;
 import de.ozgcloud.xta.client.model.XtaMessageId;
 import de.ozgcloud.xta.client.model.XtaMessageMetaDatasAndHeader;
-
+import genv3.de.xoev.transport.xta.x211.XTAService;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+@Builder(access = AccessLevel.PROTECTED)
+@Getter(AccessLevel.PROTECTED)
 public class XtaClient {
 
-	private MsgBoxServiceClient msgBoxServiceClient;
-
-	public XtaClient(final XtaClientConfig config) throws ClientInitializationException {
-		// MsgPort initialisieren
-		if (config.getMsgBoxServiceUrl() != null) {
-			msgBoxServiceClient = MsgBoxServiceClient.builder(config.getMsgBoxServiceUrl())
-					.setClientCertKeystore(config.getClientCertKeystore())
-					.setTrustCertKeystore(config.getTrustCertKeystore())
-					.setHttpProxy(config.getHttpProxy()).setSoapRequestLogging(config.isLogSoapRequest())
-					.setSoapResponseLogging(config.isLogSoapResponse()).setValidation(config.isValidation())
-					.setHttpClientPolicy(config.getHttpClientPolicy())
-					.build();
-		}
-	}
+	private final XTAService service;
+	private final XtaClientConfig config;
 
 	public XtaMessageMetaDatasAndHeader getMessagesMetadata(String xtaIdentifier) {
 		throw new NotImplementedException("");
diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
new file mode 100644
index 0000000..fbdb70d
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java
@@ -0,0 +1,32 @@
+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.XTAServiceFactory;
+import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Builder
+public class XtaClientFactory {
+
+	private final XtaConfigValidator configValidator;
+	private final XTAServiceFactory xtaServiceFactory;
+	private final XtaClientConfig config;
+
+	public static XtaClientFactory from(final XtaClientConfig config) {
+		return XtaClientFactory.builder()
+				.configValidator(XtaConfigValidator.builder().build())
+				.xtaServiceFactory(XTAServiceFactory.from(config))
+				.build();
+	}
+
+	public XtaClient create() throws ClientInitializationException {
+		configValidator.validate(config);
+		return XtaClient.builder()
+				.config(config)
+				.service(xtaServiceFactory.create())
+				.build();
+	}
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/config/HttpClientPolicy.java b/src/main/java/de/ozgcloud/xta/client/config/HttpClientPolicy.java
deleted file mode 100644
index 661f99d..0000000
--- a/src/main/java/de/ozgcloud/xta/client/config/HttpClientPolicy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- *     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.config;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.ToString;
-
-/**
- * Model-Klasse für eine HttpClientPolicy
- */
-@Getter
-@Builder
-@ToString
-public class HttpClientPolicy {
-
-  private Integer connectionTimeout;
-  private Boolean allowChunking;
-  private Integer receiveTimeOut;
-
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/config/HttpProxy.java b/src/main/java/de/ozgcloud/xta/client/config/HttpProxy.java
deleted file mode 100644
index c4fb514..0000000
--- a/src/main/java/de/ozgcloud/xta/client/config/HttpProxy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- *     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.config;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.ToString;
-
-/**
- * Model-Klasse für einen HttpProxy
- */
-@Getter
-@Builder
-@ToString(exclude = {"username", "password"})
-public class HttpProxy {
-
-  private String host;
-  private Integer port;
-  private String username;
-  private String password;
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/config/Keystore.java b/src/main/java/de/ozgcloud/xta/client/config/Keystore.java
deleted file mode 100644
index 4bb2aa6..0000000
--- a/src/main/java/de/ozgcloud/xta/client/config/Keystore.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- *     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.config;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.ToString;
-
-/**
- * Model-Klasse für einen Keystore
- */
-@Builder
-@Getter
-@ToString(exclude = {"password"})
-public class Keystore {
-
-  private String path;
-  private String password;
-
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java
index 846e663..e2b93c3 100644
--- a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java
+++ b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java
@@ -1,130 +1,61 @@
 /**
- *     XTA-Client Java Library
- *     Copyright (C) 2021 Koordinierungsstelle für IT-Standards (KoSIT)
+ * 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 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.
+ * 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/>.
+ * 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.config;
 
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
 import java.util.List;
 
-import de.ozgcloud.xta.client.exception.ClientException;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import de.ozgcloud.xta.client.model.Identifier;
-import lombok.AccessLevel;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+
+import lombok.Builder;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
-import lombok.Setter;
+import lombok.ToString;
 
-/**
- * Config-Holder for the Xta-Client.
- */
-@Getter
 @RequiredArgsConstructor
-@Setter(value = AccessLevel.PROTECTED)
+@Getter
+@Builder
+@ToString
 public class XtaClientConfig {
 
-  private List<Identifier> clientIdentifiers;
-
-  private URL managementServiceUrl;
-  private URL sendServiceUrl;
-  private URL msgBoxServiceUrl;
-  private HttpClientPolicy httpClientPolicy;
-  private HttpProxy httpProxy;
-  private Keystore clientCertKeystore;
-  private Keystore trustCertKeystore;
-
-  private boolean validation;
-  private boolean logSoapRequest;
-  private boolean logSoapResponse;
-
-
-  public static class XtaClientConfigBuilder {
-
-    private XtaClientConfig config = new XtaClientConfig();
-
-    public XtaClientConfigBuilder withValidation() {
-      config.setValidation(true);
-      return this;
-    }
-
-    public XtaClientConfigBuilder withSoapLogging() {
-      config.setLogSoapRequest(true);
-      config.setLogSoapResponse(true);
-      return this;
-    }
-
-    public XtaClientConfigBuilder withHttpClientPolicy(final HttpClientPolicy policy) {
-      config.setHttpClientPolicy(policy);
-      return this;
-    }
-
-    public XtaClientConfigBuilder withHttpProxy(final HttpProxy proxy) {
-      config.setHttpProxy(proxy);
-      return this;
-    }
-
-    public XtaClientConfig build() {
-      return this.config;
-    }
-
-  }
-
-  public static XtaClientConfigBuilder create(final URL manangementServiceUrl, final URL sendServiceUrl,
-      final URL msgBoxServiceUrl, final List<Identifier> clientIdentifiers, final String clientCertKeystore,
-      final String clientCertKeyStorePassword, final String trustCertKeystore, final String trustCertKeyStorePassword) {
-
-    final XtaClientConfigBuilder builder = new XtaClientConfigBuilder();
-
-    builder.config.setMsgBoxServiceUrl(msgBoxServiceUrl);
-    builder.config.setManagementServiceUrl(manangementServiceUrl);
-    builder.config.setSendServiceUrl(sendServiceUrl);
-    builder.config.setClientIdentifiers(clientIdentifiers);
-    builder.config
-        .setClientCertKeystore(Keystore.builder().path(clientCertKeystore).password(clientCertKeyStorePassword).build());
-    builder.config.setTrustCertKeystore(Keystore.builder().path(trustCertKeystore).password(trustCertKeyStorePassword).build());
-
-    return builder;
-  }
-
-  public static XtaClientConfigBuilder create(final XtaClientSettings settings) throws ClientException {
-    return create(List.of(Identifier.from(settings)), settings);
-  }
-
-  public static XtaClientConfigBuilder create(final List<Identifier> clientIdentifiers, final XtaClientSettings settings) throws ClientException {
-    try {
-      // Endpoint-URLs
-      final URL manangementServiceUrl = URI.create(settings.get(XtaClientSettings.BROKER_MANAGEMENT_SERVICE_ENDPONIT)).toURL();
-      final URL sendServiceUrl = URI.create(settings.get(XtaClientSettings.BROKER_SEND_SERVICE_ENDPOINT)).toURL();
-      final URL msgBoxServiceUrl = URI.create(settings.get(XtaClientSettings.BROKER_MSGBOX_SERVICE_ENDPOINT)).toURL();
-
-      // Clientcert
-      final String clientCertKeystore = settings.get(XtaClientSettings.CLIENT_KEYSTORE_CERT_PATH);
-      final String clientCertKeyStorePassword = settings.get(XtaClientSettings.CLIENT_KEYSTORE_CERT_PASSPHRASE);
-
-      // Servercert
-      final String trustCertKeystore = settings.get(XtaClientSettings.BROKER_KEYSTORE_TRUSTCERT_PATH);
-      final String trustCertKeyStorePassword = settings.get(XtaClientSettings.BROKER_KEYSTORE_TRUSTCERT_PASSPHRASE);
-
-      return create(manangementServiceUrl, sendServiceUrl, msgBoxServiceUrl, clientIdentifiers, clientCertKeystore,
-          clientCertKeyStorePassword, trustCertKeystore, trustCertKeyStorePassword);
-
-    } catch (MalformedURLException e) {
-      throw new ClientInitializationException("Some URLs could not be parsed: " + e.getMessage(), e);
-    }
-  }
+	@NotEmpty(message = "at least one client identifier is required")
+	private final List<String> clientIdentifiers;
+
+	@NotBlank
+	private final String managementServiceUrl;
+	@NotBlank
+	private final String sendServiceUrl;
+	@NotBlank
+	private final String msgBoxServiceUrl;
+
+	@Valid
+	private final KeyStore clientCertKeystore;
+
+	private final boolean schemaValidation;
+	private final boolean logSoapRequests;
+	private final boolean logSoapResponses;
+
+	@RequiredArgsConstructor
+	@Getter
+	@Builder
+	@ToString
+	public static class KeyStore {
+		@NotNull
+		private final byte[] content;
+		@NotBlank
+		private final String type;
+		@NotNull
+		private final char[] password;
+	}
 }
diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaClientSettings.java b/src/main/java/de/ozgcloud/xta/client/config/XtaClientSettings.java
deleted file mode 100644
index 159dd0d..0000000
--- a/src/main/java/de/ozgcloud/xta/client/config/XtaClientSettings.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- *     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.config;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-
-/**
- * Klasse zum Laden der Settings über eine Propertie-Datei.
- */
-public final class XtaClientSettings {
-
-  public final static String BROKER_MANAGEMENT_SERVICE_ENDPONIT = "broker.manangement-service.endpoint";
-  public final static String BROKER_SEND_SERVICE_ENDPOINT = "broker.send-service.endpoint";
-  public final static String BROKER_MSGBOX_SERVICE_ENDPOINT = "broker.msgbox-service.endpoint";
-  public final static String BROKER_KEYSTORE_TRUSTCERT_PATH = "broker.keystore.trustcert.path";
-  public final static String BROKER_KEYSTORE_TRUSTCERT_PASSPHRASE = "broker.keystore.trustcert.passphrase";
-
-  public final static String CLIENT_KEYSTORE_CERT_PATH = "client.keystore.cert.path";
-  public final static String CLIENT_KEYSTORE_CERT_PASSPHRASE = "client.keystore.cert.passphrase";
-
-  public final static String CLIENT_IDENTIFIER_ID_KEY = "client.identifier.id";
-  public final static String CLIENT_IDENTIFIER_TYPE_KEY = "client.identifier.type";
-  public final static String CLIENT_IDENTIFIER_CATEGORY_KEY = "client.identifier.category";
-  public final static String CLIENT_IDENTIFIER_NAME_KEY = "client.identifier.name";
-
-
-  private Properties props = new Properties();
-
-  public XtaClientSettings(final InputStream is) throws ClientInitializationException {
-    try {
-      props.load(is);
-      if (props.size() == 0) {
-        throw new ClientInitializationException("Properties sind nicht initialisiert.");
-      }
-    } catch (final IOException e) {
-      throw new ClientInitializationException("Properties konnten nicht geladen werden.");
-    }
-  }
-
-  public String get(String key) {
-    return (String) props.get(key);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java b/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
new file mode 100644
index 0000000..46b82cc
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/config/XtaConfigValidator.java
@@ -0,0 +1,33 @@
+package de.ozgcloud.xta.client.config;
+
+import static lombok.AccessLevel.*;
+
+import java.util.stream.Collectors;
+
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+
+import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = PRIVATE)
+@Builder
+public class XtaConfigValidator {
+	private final static Validator VALIDATOR;
+
+	static {
+		try (var factory = Validation.buildDefaultValidatorFactory()) {
+			VALIDATOR = factory.getValidator();
+		}
+	}
+
+	public void validate(final XtaClientConfig config) throws ClientInitializationException {
+		var violations = VALIDATOR.validate(config);
+		if (!violations.isEmpty()) {
+			throw new ClientInitializationException("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/EmptyNamespaceMessageOutInterceptor.java b/src/main/java/de/ozgcloud/xta/client/core/EmptyNamespaceMessageOutInterceptor.java
deleted file mode 100644
index 40560e1..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/EmptyNamespaceMessageOutInterceptor.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- *     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.core;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
-import org.apache.cxf.io.CachedOutputStream;
-import org.apache.cxf.message.Message;
-import org.apache.cxf.phase.AbstractPhaseInterceptor;
-import org.apache.cxf.phase.Phase;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Klasse um leere Namespace-Angaben zu entfernen.
- */
-@Slf4j
-class EmptyNamespaceMessageOutInterceptor extends AbstractPhaseInterceptor<Message> {
-
-  public EmptyNamespaceMessageOutInterceptor() {
-    super(Phase.PRE_STREAM);
-    addBefore(SoapPreProtocolOutInterceptor.class.getName());
-  }
-
-  public void handleMessage(Message message) {
-
-    OutputStream os = message.getContent(OutputStream.class);
-    CachedStream cs = new CachedStream();
-    message.setContent(OutputStream.class, cs);
-    message.getInterceptorChain().doIntercept(message);
-
-    try {
-      cs.flush();
-      closeQuietly(cs);
-      CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
-
-      String currentEnvelopeMessage = IOUtils
-          .toString(csnew.getInputStream(), "UTF-8");
-
-      csnew.flush();
-      closeQuietly(csnew);
-
-      if (log.isDebugEnabled()) {
-        log.debug("Outbound message: " + currentEnvelopeMessage);
-      }
-
-      String res = changeOutboundMessage(currentEnvelopeMessage);
-      if (res != null) {
-        if (log.isDebugEnabled()) {
-          log.debug("Outbound message has been changed: " + res);
-        }
-      }
-      res = res != null ? res : currentEnvelopeMessage;
-
-      InputStream replaceInStream = IOUtils
-          .toInputStream(res, "UTF-8");
-
-      IOUtils.copy(replaceInStream, os);
-      replaceInStream.close();
-      closeQuietly(replaceInStream);
-
-      os.flush();
-      message.setContent(OutputStream.class, os);
-      closeQuietly(os);
-
-    } catch (IOException ioe) {
-      log.error("Unable to perform change.", ioe);
-      throw new RuntimeException(ioe);
-    }
-  }
-
-  protected String changeOutboundMessage(String currentEnvelope) {
-    currentEnvelope = currentEnvelope.replace("xmlns=\"\"", "");
-    return currentEnvelope;
-  }
-
-  private static class CachedStream extends CachedOutputStream {
-
-    public CachedStream() {
-      super();
-    }
-  }
-
-  // WA - closeQuietly aus IOUtils ist veraltet, daher hier eine eigene Iplementierung
-  private static void closeQuietly(final Closeable closeable) {
-    try {
-      if (closeable != null) {
-        closeable.close();
-      }
-    } catch (final IOException ioe) {
-      // ignore
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClient.java b/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClient.java
deleted file mode 100644
index 0e7dea0..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClient.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * 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.core;
-
-import java.net.URL;
-
-import org.apache.cxf.ws.addressing.AttributedURIType;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import genv3.de.xoev.transport.xta.x211.CancelDeniedException;
-import genv3.de.xoev.transport.xta.x211.InvalidMessageIDException;
-import genv3.de.xoev.transport.xta.x211.LookupServiceRequest;
-import genv3.de.xoev.transport.xta.x211.LookupServiceResponse;
-import genv3.de.xoev.transport.xta.x211.ManagementPortType;
-import genv3.de.xoev.transport.xta.x211.ParameterIsNotValidException;
-import genv3.de.xoev.transport.xta.x211.PermissionDeniedException;
-import genv3.de.xoev.transport.xta.x211.TransportReport;
-import genv3.de.xoev.transport.xta.x211.XTAService;
-import genv3.de.xoev.transport.xta.x211.XTAWSTechnicalProblemException;
-import genv3.eu.osci.ws.x2014.x10.transport.PartyType;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * gewrapte Serviceklasse für den ManagementServcice eines Brokers
- */
-@Slf4j
-public class ManagementServiceClient extends WsClient<ManagementPortType> {
-
-	public static ManagementServiceClientBuilder builder(final URL serviceUrl) {
-		return new ManagementServiceClientBuilder(serviceUrl);
-	}
-
-	public ManagementServiceClient(final WsClientConfig conf) throws ClientInitializationException {
-		this.clientConfiguration = conf;
-		ManagementServiceClient.log.info(VersionInfo.getInfo());
-		ManagementServiceClient.log.info(this.clientConfiguration.toString());
-		this.initializeService();
-	}
-
-	@Override
-	void initializePort(final URL url) {
-		final XTAService service = new XTAService(url);
-		port = service.getManagementPort();
-	}
-
-	public void checkAccountActive(final PartyType authorIdentifier)
-			throws PermissionDeniedException, XTAWSTechnicalProblemException {
-		port.checkAccountActive(authorIdentifier);
-	}
-
-	public TransportReport getTransportReport(final AttributedURIType messageID, final PartyType authorIdentifier)
-			throws PermissionDeniedException, XTAWSTechnicalProblemException, InvalidMessageIDException {
-		return port.getTransportReport(messageID, authorIdentifier);
-	}
-
-	public void cancelMessage(final AttributedURIType messageID, final PartyType authorIdentifier)
-			throws XTAWSTechnicalProblemException, PermissionDeniedException, CancelDeniedException, ParameterIsNotValidException {
-		port.cancelMessage(messageID, authorIdentifier);
-	}
-
-	public LookupServiceResponse lookupService(final LookupServiceRequest lookupServiceRequest, final PartyType authorIdentifier)
-			throws ParameterIsNotValidException, PermissionDeniedException, XTAWSTechnicalProblemException {
-		return port.lookupService(lookupServiceRequest, authorIdentifier);
-	}
-
-	public AttributedURIType createMessageId(final PartyType authorIdentifier)
-			throws PermissionDeniedException, XTAWSTechnicalProblemException {
-		return port.createMessageId(authorIdentifier);
-	}
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClientBuilder.java b/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClientBuilder.java
deleted file mode 100644
index 6612aa8..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/ManagementServiceClientBuilder.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Konstruiert eine Instanz des {@link WsClient}.
- */
-@Getter(AccessLevel.PACKAGE)
-@Slf4j
-public class ManagementServiceClientBuilder extends ServiceBuilder<ManagementServiceClient> {
-
-  /**
-   * Initialisiert einen {@link ServiceBuilder} mit ServiceUrl-Host und ServiceUrl-Port.
-   *
-   * @param serviceUrl URL des Endpoints
-   */
-  ManagementServiceClientBuilder(final URL serviceUrl) {
-    super(serviceUrl);
-  }
-
-  /**
-   * Erstellt eine Instanz vom Typ AnlieferungServiceClient
-   *
-   * @return ManagementServiceClient
-   * @throws ClientInitializationException {@link ClientInitializationException}
-   */
-
-  @Override
-  public ManagementServiceClient build() throws ClientInitializationException {
-    this.validateConfiguration();
-    return new ManagementServiceClient(this.clientConfiguration);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClient.java b/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClient.java
deleted file mode 100644
index 4db3dbe..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClient.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import jakarta.xml.ws.Holder;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import genv3.de.xoev.transport.xta.x211.*;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgBoxCloseRequestType;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgBoxFetchRequest;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgBoxGetNextRequestType;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgBoxResponseType;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgBoxStatusListRequestType;
-import genv3.eu.osci.ws.x2008.x05.transport.MsgStatusListType;
-import genv3.eu.osci.ws.x2014.x10.transport.MessageMetaData;
-import genv3.eu.osci.ws.x2014.x10.transport.PartyType;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * gewrapte Serviceklasse für den MsgBoxServcice eines Brokers
- */
-@Slf4j
-public class MsgBoxServiceClient extends WsClient<MsgBoxPortType> {
-
-  public static MsgBoxServiceClientBuilder builder(final URL serviceUrl) {
-    return new MsgBoxServiceClientBuilder(serviceUrl);
-  }
-
-  public MsgBoxServiceClient(final WsClientConfig conf) throws ClientInitializationException {
-
-    this.clientConfiguration = conf;
-    MsgBoxServiceClient.log.info(VersionInfo.getInfo());
-    MsgBoxServiceClient.log.info(this.clientConfiguration.toString());
-    this.initializeService();
-  }
-
-  @Override
-  void initializePort(final URL url) {
-    final XTAService service = new XTAService(url);
-    port = service.getMsgBoxPort();
-  }
-
-  public GenericContentContainer getMessage(final MsgBoxFetchRequest fetchRequest, final PartyType authorIdentifier,
-      final Holder<MessageMetaData> messageMetaData, final Holder<MsgBoxResponseType> fetchResponseHeader)
-      throws InvalidMessageIDException, PermissionDeniedException, XTAWSTechnicalProblemException {
-    return port.getMessage(fetchRequest, authorIdentifier, messageMetaData, fetchResponseHeader);
-  }
-
-  public MsgStatusListType getStatusList(final MsgBoxStatusListRequestType fetchRequest, final PartyType authorIdentifier,
-      final Holder<MsgBoxResponseType> fetchResponseHeader) throws PermissionDeniedException, XTAWSTechnicalProblemException {
-    return port.getStatusList(fetchRequest, authorIdentifier, fetchResponseHeader);
-  }
-
-  public GenericContentContainer getNextMessage(final MsgBoxGetNextRequestType fetchRequest, final PartyType authorIdentifier,
-      final Holder<MessageMetaData> messageMetaData, final Holder<MsgBoxResponseType> fetchResponseHeader)
-      throws InvalidMessageIDException, PermissionDeniedException, XTAWSTechnicalProblemException {
-    return port.getNextMessage(fetchRequest, authorIdentifier, messageMetaData, fetchResponseHeader);
-  }
-
-  public MsgStatusListType getNextStatusList(final MsgBoxGetNextRequestType fetchRequest, final PartyType authorIdentifier,
-      final Holder<MsgBoxResponseType> fetchResponseHeader) throws PermissionDeniedException, XTAWSTechnicalProblemException {
-    return port.getNextStatusList(fetchRequest, authorIdentifier, fetchResponseHeader);
-  }
-
-  public void close(final MsgBoxCloseRequestType fetchRequest, final PartyType authorIdentifier)
-      throws InvalidMessageIDException, PermissionDeniedException, XTAWSTechnicalProblemException {
-    port.close(fetchRequest, authorIdentifier);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClientBuilder.java b/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClientBuilder.java
deleted file mode 100644
index bc46414..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/MsgBoxServiceClientBuilder.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Konstruiert eine Instanz des {@link WsClient}.
- */
-@Getter(AccessLevel.PACKAGE)
-@Slf4j
-public class MsgBoxServiceClientBuilder extends ServiceBuilder<MsgBoxServiceClient> {
-
-  /**
-   * Initialisiert einen {@link ServiceBuilder} mit ServiceUrl-Host und ServiceUrl-Port.
-   *
-   * @param serviceUrl URL des Endpoints
-   */
-  MsgBoxServiceClientBuilder(final URL serviceUrl) {
-    super(serviceUrl);
-  }
-
-  /**
-   * Erstellt eine Instanz vom Typ AnlieferungServiceClient
-   *
-   * @return ManagementServiceClient
-   * @throws ClientInitializationException {@link ClientInitializationException}
-   */
-  @Override
-  public MsgBoxServiceClient build() throws ClientInitializationException {
-    this.validateConfiguration();
-    return new MsgBoxServiceClient(this.clientConfiguration);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/SendServiceClient.java b/src/main/java/de/ozgcloud/xta/client/core/SendServiceClient.java
deleted file mode 100644
index c5c1825..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/SendServiceClient.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import jakarta.xml.ws.Holder;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import genv3.de.xoev.transport.xta.x211.GenericContentContainer;
-import genv3.de.xoev.transport.xta.x211.MessageSchemaViolationException;
-import genv3.de.xoev.transport.xta.x211.MessageVirusDetectionException;
-import genv3.de.xoev.transport.xta.x211.ParameterIsNotValidException;
-import genv3.de.xoev.transport.xta.x211.PermissionDeniedException;
-import genv3.de.xoev.transport.xta.x211.SendPortType;
-import genv3.de.xoev.transport.xta.x211.SyncAsyncException;
-import genv3.de.xoev.transport.xta.x211.XTAService;
-import genv3.de.xoev.transport.xta.x211.XTAWSTechnicalProblemException;
-import genv3.eu.osci.ws.x2008.x05.transport.X509TokenContainerType;
-import genv3.eu.osci.ws.x2014.x10.transport.MessageMetaData;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * gewrapte Serviceklasse für den SendServcice eines Brokers
- */
-@Slf4j
-public class SendServiceClient extends WsClient<SendPortType> {
-
-  public static SendServiceClientBuilder builder(final URL serviceUrl) {
-    return new SendServiceClientBuilder(serviceUrl);
-  }
-
-  public SendServiceClient(final WsClientConfig conf) throws ClientInitializationException {
-    this.clientConfiguration = conf;
-    SendServiceClient.log.info(VersionInfo.getInfo());
-    SendServiceClient.log.info(this.clientConfiguration.toString());
-    this.initializeService();
-  }
-
-  @Override
-  void initializePort(final URL url) {
-    final XTAService service = new XTAService(url);
-    port = service.getSendXtaPort();
-  }
-
-  public void sendMessage(final GenericContentContainer genericContainer, final MessageMetaData messageMetaData,
-      final X509TokenContainerType x509TokenContainer)
-      throws PermissionDeniedException, XTAWSTechnicalProblemException, MessageSchemaViolationException, MessageVirusDetectionException,
-          ParameterIsNotValidException, SyncAsyncException {
-    port.sendMessage(genericContainer, messageMetaData, x509TokenContainer);
-  }
-
-  public void sendMessageSync(final Holder<GenericContentContainer> genericContainer,
-      final Holder<MessageMetaData> messageMetaData, final Holder<X509TokenContainerType> x509TokenContainer)
-      throws PermissionDeniedException, XTAWSTechnicalProblemException, MessageSchemaViolationException, MessageVirusDetectionException, ParameterIsNotValidException, SyncAsyncException {
-    port.sendMessageSync(genericContainer, messageMetaData, x509TokenContainer);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/SendServiceClientBuilder.java b/src/main/java/de/ozgcloud/xta/client/core/SendServiceClientBuilder.java
deleted file mode 100644
index 8de3e05..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/SendServiceClientBuilder.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Konstruiert eine Instanz des {@link WsClient}.
- */
-@Getter(AccessLevel.PACKAGE)
-@Slf4j
-public class SendServiceClientBuilder extends ServiceBuilder<SendServiceClient> {
-
-  /**
-   * Initialisiert einen {@link ServiceBuilder} mit ServiceUrl-Host und ServiceUrl-Port.
-   *
-   * @param serviceUrl URL des Endpoints
-   */
-  SendServiceClientBuilder(final URL serviceUrl) {
-    super(serviceUrl);
-  }
-
-  /**
-   * Erstellt eine Instanz vom Typ AnlieferungServiceClient
-   *
-   * @return ManagementServiceClient
-   * @throws ClientInitializationException {@link ClientInitializationException}
-   */
-
-  @Override
-  public SendServiceClient build() throws ClientInitializationException {
-    this.validateConfiguration();
-    return new SendServiceClient(this.clientConfiguration);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/ServiceBuilder.java b/src/main/java/de/ozgcloud/xta/client/core/ServiceBuilder.java
deleted file mode 100644
index aa9efc3..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/ServiceBuilder.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-import java.util.List;
-
-import org.apache.commons.lang3.StringUtils;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-
-import de.ozgcloud.xta.client.config.HttpClientPolicy;
-import de.ozgcloud.xta.client.config.HttpProxy;
-import de.ozgcloud.xta.client.config.Keystore;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Konstruiert eine Instanz des {@link WsClient}.
- */
-@Getter(AccessLevel.PACKAGE)
-@Slf4j
-public abstract class ServiceBuilder<T extends WsClient> {
-
-  @Getter(AccessLevel.NONE)
-  final WsClientConfig clientConfiguration;
-
-  /**
-   * Initialisiert einen {@link ServiceBuilder} mit ServiceUrl-Host und ServiceUrl-Port.
-   *
-   * @param serviceUrl URL des Endpoints
-   */
-  ServiceBuilder(final URL serviceUrl) {
-    this.clientConfiguration = new WsClientConfig();
-    this.clientConfiguration.setServiceUrl(serviceUrl);
-  }
-
-  /**
-   * Sezt Trust-Keystore des {@link WsClient}s.
-   *
-   * @param keystore Trust-Keystore
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setTrustCertKeystore(final Keystore keystore) {
-    this.clientConfiguration.setTrustCertKeystore(keystore);
-    return this;
-  }
-
-  /**
-   * Setzt den Client-Keystore des {@link WsClient}s.
-   *
-   * @param keystore Client-Keystore
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setClientCertKeystore(final Keystore keystore) {
-    this.clientConfiguration.setClientCertKeystore(keystore);
-    return this;
-  }
-
-  /**
-   * Setzt den Proxy Host.
-   *
-   * @param proxy Proxy Host
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setHttpProxy(final HttpProxy proxy) {
-    this.clientConfiguration.setHttpProxy(proxy);
-    return this;
-  }
-
-  /**
-   * Setzt XSD Validation
-   *
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setValidation(final boolean validation) {
-    this.clientConfiguration.setValidation(validation);
-    return this;
-  }
-
-  public ServiceBuilder<T> enableValidation() {
-    setValidation(true);
-    return this;
-  }
-
-  public ServiceBuilder<T> enableSoapLogging() {
-    setSoapRequestLogging(true);
-    setSoapResponseLogging(true);
-    return this;
-  }
-
-  /**
-   * Setzt SoapRequest Logging
-   *
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setSoapRequestLogging(final boolean logging) {
-    this.clientConfiguration.setLogSoapRequest(logging);
-    return this;
-  }
-
-  /**
-   * Setzt SoapResponse Logging
-   *
-   * @return {@link ServiceBuilder}
-   */
-  public ServiceBuilder<T> setSoapResponseLogging(final boolean logging) {
-    this.clientConfiguration.setLogSoapResponse(logging);
-    return this;
-  }
-
-  /**
-   * Setzt eine HttpClient-Policy
-   *
-   * @param policy HttpClientPolicy
-   * @return {@link ServiceBuilder}
-   */
-
-  public ServiceBuilder<T> setHttpClientPolicy(final HttpClientPolicy policy) {
-    this.clientConfiguration.setHttpClientPolicy(policy);
-    return this;
-  }
-
-  void validateConfiguration() {
-    final List<String> errorMsgs = Lists.newArrayList();
-    ServiceBuilder.checkNotNull("Service-URL", this.clientConfiguration.getServiceUrl(), errorMsgs);
-
-    // Entweder beide Angaben leer oder beide gesetzt
-    if (this.clientConfiguration.getHttpProxy() != null) {
-      if (this.clientConfiguration.getHttpProxy().getHost() == null ^ this.clientConfiguration.getHttpProxy().getPort() == 0) {
-        errorMsgs.add("Angaben zum Proxy (Host, Port) müssen vollständig gesetzt sein");
-      }
-    }
-
-    this.checkKeystoreFile("Client-Keystore", this.clientConfiguration.getClientCertKeystore(), errorMsgs);
-    this.checkKeystoreFile("Trust-Keystore", this.clientConfiguration.getTrustCertKeystore(), errorMsgs);
-
-    if (errorMsgs != null && !errorMsgs.isEmpty()) {
-      throw new IllegalArgumentException(Joiner.on("\n").join(errorMsgs));
-    }
-  }
-
-  private static void checkNotNull(final String identifier, final Object value, final List<String> errorMsgs) {
-    if (value == null) {
-      errorMsgs.add(identifier + ": Wert muss gesetzt sein");
-    }
-  }
-
-  private void checkKeystoreFile(final String keyStoreIdentifier, final Keystore keyStore,
-      final List<String> errorMsgs) {
-    if (StringUtils.isEmpty(keyStore.getPath())) {
-      errorMsgs.add(keyStoreIdentifier + ": Pfad muss gesetzt sein");
-    }
-    ServiceBuilder.checkNotNull(keyStoreIdentifier + " Passwort", keyStore.getPassword(), errorMsgs);
-  }
-
-  public abstract T build() throws ClientInitializationException;
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/VersionInfo.java b/src/main/java/de/ozgcloud/xta/client/core/VersionInfo.java
deleted file mode 100644
index b711992..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/VersionInfo.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- *     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.core;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Klasse mit VersionInfo
- */
-@Slf4j
-@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
-public final class VersionInfo {
-
-  private static String build;
-
-  private static String buildTimestamp;
-
-  private static String version;
-
-  public static String getInfo() {
-    return String.format("VersionInfo: version: %s, build: %s, timestamp: %s", VersionInfo.version, VersionInfo.build,
-        VersionInfo.buildTimestamp);
-  }
-
-  static {
-    final Properties p = new Properties();
-    try (final InputStream in = VersionInfo.class.getClassLoader().getResourceAsStream("version.properties")) {
-
-      p.load(in);
-
-      VersionInfo.build = p.getProperty("bamboo_buildNumber");
-      VersionInfo.buildTimestamp = p.getProperty("build_creation_timestamp");
-      VersionInfo.version = p.getProperty("project_version");
-
-    } catch (final IOException e) {
-      log.error(e.getMessage(), e);
-      throw new IllegalStateException("Can not initialize version information");
-    }
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/WsClient.java b/src/main/java/de/ozgcloud/xta/client/core/WsClient.java
deleted file mode 100644
index 2b65bf4..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/WsClient.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-import jakarta.xml.ws.BindingProvider;
-
-import org.apache.cxf.common.util.StringUtils;
-import org.apache.cxf.configuration.jsse.TLSClientParameters;
-import org.apache.cxf.endpoint.Client;
-import org.apache.cxf.ext.logging.LoggingInInterceptor;
-import org.apache.cxf.ext.logging.LoggingOutInterceptor;
-import org.apache.cxf.frontend.ClientProxy;
-import org.apache.cxf.transport.http.HTTPConduit;
-import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
-
-import de.ozgcloud.xta.client.config.HttpClientPolicy;
-import de.ozgcloud.xta.client.config.HttpProxy;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import de.ozgcloud.xta.client.util.KeyStoreUtil;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Basisklasse für gewrapte Serviceklassen
- */
-@Slf4j
-abstract class WsClient<T> {
-
-  WsClientConfig clientConfiguration;
-
-  T port;
-
-  abstract void initializePort(final URL url);
-
-  void initializeService() throws ClientInitializationException {
-
-    final String logMessage =
-        "JarInfo: " + this.getClass().getName() + ",\nConfiguration: " + this.clientConfiguration + "," + "\ninitializeService";
-
-    try {
-      final String serviceUrl = this.clientConfiguration.getServiceUrl().toString();
-      WsClient.log.debug("Initialize Service {}", serviceUrl);
-      final URL url = this.getClass().getClassLoader().getResource("wsdl/XTA.wsdl");
-      WsClient.log.debug("url: {}", url);
-      this.initializeServiceEndpoint(url);
-      this.initializeTransportSecurity();
-      this.initializeHttpClientPolicy();
-      this.initializeProxy();
-      WsClient.log.info("{} sucessfully.", logMessage);
-    } catch (final Exception ex) {
-      WsClient.log.error("{} failed. Reason: {}", logMessage, ex.getMessage());
-      throw new ClientInitializationException(ex.getMessage(), ex);
-    }
-  }
-
-  private void initializeServiceEndpoint(final URL url) {
-    initializePort(url);
-    final BindingProvider bp = (BindingProvider) this.port;
-
-    bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, this.clientConfiguration.getServiceUrl().toString());
-
-    WsClient.log.debug("Set EnpointUrl to:  {}", this.clientConfiguration.getServiceUrl().toString());
-
-    // Empty xmlns="" entfernen -> XSD-Modellierung Annonymous Inner Type
-    // ClientProxy.getClient(port).getOutInterceptors().add(new EmptyNamespaceMessageOutInterceptor());
-
-    initializeValidation();
-
-    if (this.clientConfiguration.isLogSoapRequest()) {
-      LoggingOutInterceptor loi = new LoggingOutInterceptor();
-      loi.setPrettyLogging(true);
-      ClientProxy.getClient(port).getOutInterceptors().add(loi);
-      WsClient.log.debug("Set SoapReqeust-Logging: {}", clientConfiguration.isLogSoapRequest());
-    }
-
-    if (this.clientConfiguration.isLogSoapResponse()) {
-      LoggingInInterceptor lii = new LoggingInInterceptor();
-      lii.setPrettyLogging(true);
-      ClientProxy.getClient(port).getInInterceptors().add(lii);
-      WsClient.log.debug("Set SoapResponse-Logging: {}", clientConfiguration.isLogSoapResponse());
-    }
-  }
-
-  private void initializeHttpClientPolicy() {
-    final HttpClientPolicy policy = this.clientConfiguration.getHttpClientPolicy();
-    if (policy != null) {
-      final Client client = ClientProxy.getClient(this.port);
-      final HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
-      final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
-      if (policy.getConnectionTimeout() != null) {
-        httpClientPolicy.setConnectionTimeout(policy.getConnectionTimeout());
-      }
-      if (policy.getAllowChunking() != null) {
-        httpClientPolicy.setAllowChunking(policy.getAllowChunking());
-      }
-      if (policy.getReceiveTimeOut() != null) {
-        httpClientPolicy.setReceiveTimeout(policy.getReceiveTimeOut());
-      }
-      httpConduit.setClient(httpClientPolicy);
-      WsClient.log.debug("Intitialize HttpClientPolicy");
-    }
-  }
-
-  private void initializeTransportSecurity() throws ClientInitializationException {
-    if (this.clientConfiguration.getClientCertKeystore() != null) {
-      final Client client = ClientProxy.getClient(this.port);
-      final HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
-      final TLSClientParameters parameters = new TLSClientParameters();
-      parameters.setSSLSocketFactory(this.createSSLContext().getSocketFactory());
-      httpConduit.setTlsClientParameters(parameters);
-      WsClient.log.debug("Intitialize TransportSecurity");
-    }
-  }
-
-  private void initializeProxy() {
-    final HttpProxy proxy = this.clientConfiguration.getHttpProxy();
-    if (proxy != null) {
-      final Client client = ClientProxy.getClient(this.port);
-      final HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
-      if (!StringUtils.isEmpty(proxy.getHost())) {
-        httpConduit.getClient().setProxyServer(proxy.getHost());
-      }
-      if (proxy.getPort() != null) {
-        httpConduit.getClient().setProxyServerPort(proxy.getPort());
-      }
-      if (!StringUtils.isEmpty(proxy.getUsername())) {
-        httpConduit.getProxyAuthorization().setUserName(proxy.getUsername());
-      }
-      if (!StringUtils.isEmpty(proxy.getPassword())) {
-        httpConduit.getProxyAuthorization().setPassword(proxy.getPassword());
-      }
-      WsClient.log.debug("Intitialize Proxy");
-    }
-  }
-
-  private SSLContext createSSLContext() throws ClientInitializationException {
-    try {
-
-      // Server
-      final KeyStore trustStore = KeyStoreUtil
-          .load(this.clientConfiguration.getTrustCertKeystore().getPath(),
-              this.clientConfiguration.getTrustCertKeystore().getPassword());
-
-      // client
-      final KeyStore keyStore = KeyStoreUtil
-          .load(this.clientConfiguration.getClientCertKeystore().getPath(),
-              this.clientConfiguration.getClientCertKeystore().getPassword());
-
-      //Client-Lib Truststore
-      final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-      tmf.init(trustStore);
-
-      //Default JDK Truststore
-      final TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-      defaultTmf.init((KeyStore) null);
-
-      //Client-Lib-Keystore
-      final KeyManagerFactory kmf = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-      kmf.init(keyStore, this.clientConfiguration.getClientCertKeystore().getPassword().toCharArray());
-
-      final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
-
-      CustomTruststoreManager customTruststoreManager = new CustomTruststoreManager(getX509TrustMananger(defaultTmf),
-          getX509TrustMananger(tmf));
-
-      sslContext.init(kmf.getKeyManagers(), new TrustManager[]{customTruststoreManager}, new SecureRandom());
-
-      return sslContext;
-    } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
-      throw new ClientInitializationException("Exception in createSSLContext: " + e.getMessage(), e);
-    }
-  }
-
-  private X509TrustManager getX509TrustMananger(TrustManagerFactory tmf) {
-    for (TrustManager tm : tmf.getTrustManagers()) {
-      if (tm instanceof X509TrustManager) {
-        return (X509TrustManager) tm;
-      }
-    }
-
-    return null;
-  }
-
-  public void enableValidation() {
-    clientConfiguration.setValidation(true);
-    initializeValidation();
-  }
-
-  public void disableValidation() {
-    clientConfiguration.setValidation(false);
-    initializeValidation();
-  }
-
-  private void initializeValidation() {
-    final BindingProvider bp = (BindingProvider) this.port;
-
-    if (this.clientConfiguration.isValidation()) {
-      bp.getRequestContext().put("schema-validation-enabled", true);
-    } else {
-      bp.getRequestContext().put("schema-validation-enabled", false);
-    }
-
-    WsClient.log.debug("Set Enpoint-Validation: {}", clientConfiguration.isValidation());
-  }
-
-  @RequiredArgsConstructor
-  private class CustomTruststoreManager implements X509TrustManager {
-
-    final X509TrustManager defaultTm;
-    final X509TrustManager myTm;
-
-    @Override
-    public X509Certificate[] getAcceptedIssuers() {
-      // If you're planning to use client-cert auth,
-      // merge results from "defaultTm" and "myTm".
-      return defaultTm.getAcceptedIssuers();
-    }
-
-    @Override
-    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-      try {
-        myTm.checkServerTrusted(chain, authType);
-      } catch (CertificateException e) {
-        defaultTm.checkServerTrusted(chain, authType);
-      }
-    }
-
-    @Override
-    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-      // If you're planning to use client-cert auth,
-      // do the same as checking the server.
-      defaultTm.checkClientTrusted(chain, authType);
-    }
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/WsClientConfig.java b/src/main/java/de/ozgcloud/xta/client/core/WsClientConfig.java
deleted file mode 100644
index cb46664..0000000
--- a/src/main/java/de/ozgcloud/xta/client/core/WsClientConfig.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *     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.core;
-
-import java.net.URL;
-
-import de.ozgcloud.xta.client.config.HttpClientPolicy;
-import de.ozgcloud.xta.client.config.HttpProxy;
-import de.ozgcloud.xta.client.config.Keystore;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
-
-/**
- * Konfigurationsparameter für den {@link WsClient}.
- */
-
-@Setter(AccessLevel.PACKAGE)
-@Getter(AccessLevel.PACKAGE)
-@ToString
-class WsClientConfig {
-
-  private URL serviceUrl;
-  private Keystore clientCertKeystore;
-  private Keystore trustCertKeystore;
-  private HttpProxy httpProxy;
-  private HttpClientPolicy httpClientPolicy;
-
-  private boolean validation = false;
-  private boolean logSoapRequest = false;
-  private boolean logSoapResponse = false;
-
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/core/XTAServiceFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XTAServiceFactory.java
new file mode 100644
index 0000000..fbc3ae3
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/core/XTAServiceFactory.java
@@ -0,0 +1,106 @@
+package de.ozgcloud.xta.client.core;
+
+import java.util.List;
+import java.util.Map;
+
+import jakarta.xml.ws.BindingProvider;
+
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.ext.logging.LoggingInInterceptor;
+import org.apache.cxf.ext.logging.LoggingOutInterceptor;
+import org.apache.cxf.frontend.ClientProxy;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.transport.http.HTTPConduit;
+
+import de.ozgcloud.xta.client.XtaClient;
+import de.ozgcloud.xta.client.config.XtaClientConfig;
+import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import genv3.de.xoev.transport.xta.x211.XTAService;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Builder
+@RequiredArgsConstructor
+@Slf4j
+public class XTAServiceFactory {
+
+	final static String SCHEMA_VALIDATION_ENABLED_KEY = "schema-validation-enabled";
+
+	private final XtaClientConfig config;
+	private final XtaTLSClientParametersFactory sslContextFactory;
+
+	public static XTAServiceFactory from(final XtaClientConfig config) {
+		return XTAServiceFactory.builder()
+				.config(config)
+				.sslContextFactory(XtaTLSClientParametersFactory.builder()
+						.config(config)
+						.build())
+				.build();
+	}
+
+	public XTAService create() throws ClientInitializationException {
+		log.debug("[createXtaService] Using config: {}", config);
+		var service = new XTAService(XtaClient.class.getResource("wsdl/XTA.wsdl"));
+		configureServiceBindings(service);
+		return service;
+	}
+
+	void configureServiceBindings(final XTAService service) throws ClientInitializationException {
+		configureBinding(config.getManagementServiceUrl(), (BindingProvider) service.getManagementPort());
+		configureBinding(config.getSendServiceUrl(), (BindingProvider) service.getSendXtaPort());
+		configureBinding(config.getMsgBoxServiceUrl(), (BindingProvider) service.getMsgBoxPort());
+	}
+
+	void configureBinding(final String endpointUrl, final BindingProvider port) throws ClientInitializationException {
+		configureRequestContext(endpointUrl, port.getRequestContext());
+		configureClient(getClientFromPort(port));
+	}
+
+	Client getClientFromPort(BindingProvider port) {
+		return ClientProxy.getClient(port);
+	}
+
+	void configureRequestContext(final String endpointUrl, Map<String, Object> requestContext) {
+		requestContext.putAll(Map.of(
+				BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointUrl,
+				SCHEMA_VALIDATION_ENABLED_KEY, config.isSchemaValidation()
+		));
+	}
+
+	void configureClient(Client client) throws ClientInitializationException {
+		if (config.isLogSoapRequests()) {
+			configureOutInterceptors(client.getOutInterceptors());
+		}
+		if (config.isLogSoapResponses()) {
+			configureInInterceptors(client.getInInterceptors());
+		}
+		configureHttpConduit((HTTPConduit) client.getConduit());
+		log.debug("[configureClient] Initialized TransportSecurity");
+	}
+
+	void configureInInterceptors(List<Interceptor<? extends Message>> inInterceptors) {
+		inInterceptors.add(createInInterceptor());
+	}
+
+	void configureOutInterceptors(List<Interceptor<? extends Message>> outInterceptors) {
+		outInterceptors.add(createOutInterceptor());
+	}
+
+	private LoggingOutInterceptor createOutInterceptor() {
+		var outInterceptor = new LoggingOutInterceptor();
+		outInterceptor.setPrettyLogging(true);
+		return outInterceptor;
+	}
+
+	private LoggingInInterceptor createInInterceptor() {
+		var inInterceptor = new LoggingInInterceptor();
+		inInterceptor.setPrettyLogging(true);
+		return inInterceptor;
+	}
+
+	void configureHttpConduit(HTTPConduit conduit) throws ClientInitializationException {
+		conduit.setTlsClientParameters(sslContextFactory.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
new file mode 100644
index 0000000..b9e9285
--- /dev/null
+++ b/src/main/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactory.java
@@ -0,0 +1,68 @@
+package de.ozgcloud.xta.client.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.SSLContext;
+
+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 lombok.Builder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Builder
+@RequiredArgsConstructor
+@Slf4j
+public class XtaTLSClientParametersFactory {
+
+	final static String TLS_PROTOCOL = "TLSv1.2";
+
+	private final XtaClientConfig config;
+
+	public TLSClientParameters create() throws ClientInitializationException {
+		log.debug("[createXtaTLsParameters] Using config: {}", config);
+		var sslContext = createXtaSslContext();
+
+		var parameters = new TLSClientParameters();
+		parameters.setSSLSocketFactory(sslContext.getSocketFactory());
+		return parameters;
+	}
+
+	SSLContext createXtaSslContext() throws ClientInitializationException {
+		try {
+			var sslContextBuilder = createSSLContextBuilder();
+			var clientCertKeystore = config.getClientCertKeystore();
+			if (clientCertKeystore != null) {
+				sslContextBuilder.loadKeyMaterial(loadStore(clientCertKeystore), clientCertKeystore.getPassword());
+			}
+			return sslContextBuilder.build();
+		} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException | UnrecoverableKeyException | IOException |
+				CertificateException e) {
+			throw new ClientInitializationException("Failed to create SSL context: " + e.getMessage(), e);
+		}
+	}
+
+	SSLContextBuilder createSSLContextBuilder() {
+		return SSLContextBuilder.create()
+				.setProtocol(TLS_PROTOCOL);
+	}
+
+	KeyStore loadStore(final XtaClientConfig.KeyStore keystore)
+			throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
+		var keyStore = KeyStore.getInstance(keystore.getType());
+		try (var is = new ByteArrayInputStream(keystore.getContent())) {
+			keyStore.load(is, keystore.getPassword());
+		}
+		return keyStore;
+	}
+}
diff --git a/src/main/java/de/ozgcloud/xta/client/model/Identifier.java b/src/main/java/de/ozgcloud/xta/client/model/Identifier.java
deleted file mode 100644
index 0f10654..0000000
--- a/src/main/java/de/ozgcloud/xta/client/model/Identifier.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- *     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.model;
-
-import org.apache.commons.lang3.StringUtils;
-
-import de.ozgcloud.xta.client.codes.IdentifierType;
-import de.ozgcloud.xta.client.config.XtaClientSettings;
-import de.ozgcloud.xta.client.exception.ClientException;
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-/**
- * Modelklasse, repräsentiert einen Kommunikationspartner, bestehend aus Kennung, Typ, Kategorie (optinal) und einen
- * benutzerfreundlichen Namen (optional). Diese Klasse definiert nicht die (technische) Rolle des Partners wie Leser oder Autor,
- * bzw. Sender oder Empfänger, sondern kann für beides stehen.
- */
-@Getter
-@NoArgsConstructor(access = AccessLevel.PUBLIC)
-public class Identifier {
-
-  // Pflichtfeld: die Kennung, üblich <Prefix>:<Kennung>
-  private String id;
-
-  // Pflichtfeld: Typ der Kennung, z.B.: XOEV oder JUSTIZ
-  private String type;
-
-  // Optional: benutzerfreundlicher Name, "Anzeigename"
-  private String name;
-
-  // Optional: Konkretesiert die fachliche Rolle eines Partners genauer (e.g. "buyer", "Meldehörde", "Standesamt"...)
-  private String category;
-
-  /**
-   * Erzeugen eines Identifier mit seinen minimalen Pflichtfeldern.
-   *
-   * @param id   die ("technische") Kennung des Kommunikationspartners, zb. xdo:11009991
-   * @param type Typ des Kommunikationspartners, zb. XOEV
-   */
-  public Identifier(final String id, String type) throws ClientInitializationException {
-    this.id = id;
-    this.type = type;
-
-    assertIsValid(this, "Constructor");
-  }
-
-  /**
-   * Erzeugen eines Identifier mit seinen minimalen Pflichtfeldern.
-   *
-   * @param id   die ("technische") Kennung des Kommunikationspartners, zb. xdo:11009991
-   * @param type Typ des Kommunikationspartners, zb. XOEV, {@link IdentifierType}
-   */
-  public Identifier(final String id, IdentifierType type) throws ClientInitializationException {
-    this(id, type.getCode());
-  }
-
-  /**
-   * Statische Fabrik-Methode zum Erzeugen einer Identifier-Instanz aus den Einstellungen (settings.properties). Typischerweise
-   * ist dies die Identität des XtaClients selbst.
-   *
-   * @return eine Identifier-Instanz gefüllt mit den Daten der Einstellungen.
-   * @throws ClientException wenn die erzeugte Instans nicht valide ist oder es probleme mit dem Settingfile gibt (IO)
-   */
-  public static Identifier from(final XtaClientSettings settings) throws ClientException {
-    Identifier target = new Identifier();
-    target.id = settings.get(XtaClientSettings.CLIENT_IDENTIFIER_ID_KEY);
-    target.type = settings.get(XtaClientSettings.CLIENT_IDENTIFIER_TYPE_KEY);
-    target.category = settings.get(XtaClientSettings.CLIENT_IDENTIFIER_CATEGORY_KEY);
-    target.name = settings.get(XtaClientSettings.CLIENT_IDENTIFIER_NAME_KEY);
-
-    assertIsValid(target, "Settings");
-    return target;
-  }
-
-  /**
-   * Mit einem optionalen "Nutzerfreundlichen", zb. "Heiko Materny"
-   *
-   * @param name der Name
-   * @return Identifier-Instanz ("sich selbst"") gefüllt mit dem Namen.
-   */
-  public Identifier withUserFriendlyName(String name) {
-    this.name = name;
-    return this;
-  }
-
-  /**
-   * Mit einer optionalen Kategorie, zb. "Standesamt"
-   *
-   * @param category die Kategorie, e.g. Standesamt
-   * @return Identifier-Instanz ("sich selbst"") gefüllt mit der Kategorie.
-   */
-  public Identifier withCategory(String category) {
-    this.name = category;
-    return this;
-  }
-
-  private static void assertIsValid(Identifier toCheck, String errMsg) throws ClientInitializationException {
-    if (StringUtils.isEmpty(toCheck.id)) {
-      throw new ClientInitializationException("construction from " + errMsg + " failed, reason: id is empty");
-    }
-
-    if (StringUtils.isEmpty(toCheck.type)) {
-      throw new ClientInitializationException("construction from " + errMsg + " failed, reason: type is empty");
-    }
-  }
-
-  /**
-   * Enum zur Auswahl der Kommunikationsrolle (spielt nur eine Rolle wenn der innere Nachrichtenkopf verarbeitet wird.)
-   */
-  public enum CommunicationRole {
-    AUTHOR, READER, REPLYTO
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/util/FileUtil.java b/src/main/java/de/ozgcloud/xta/client/util/FileUtil.java
deleted file mode 100644
index e9b35d1..0000000
--- a/src/main/java/de/ozgcloud/xta/client/util/FileUtil.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- *     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.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-/**
- * Utility-Klasse für File-Operationen
- */
-@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
-@NoArgsConstructor(access = AccessLevel.NONE)
-public final class FileUtil {
-
-  public static InputStream loadByName(String name) throws IOException {
-    final File f = new File(name);
-    if (f.isFile()) {
-      return new FileInputStream(f);
-    } else {
-      return FileUtil.class.getClassLoader().getResourceAsStream(name);
-    }
-  }
-}
diff --git a/src/main/java/de/ozgcloud/xta/client/util/KeyStoreUtil.java b/src/main/java/de/ozgcloud/xta/client/util/KeyStoreUtil.java
deleted file mode 100644
index 88396b0..0000000
--- a/src/main/java/de/ozgcloud/xta/client/util/KeyStoreUtil.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- *     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.util;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-import org.apache.commons.lang3.StringUtils;
-
-import de.ozgcloud.xta.client.exception.ClientInitializationException;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-/**
- * Hilfsklasse für KeyStore-Handling.
- */
-@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
-@NoArgsConstructor(access = AccessLevel.NONE)
-public final class KeyStoreUtil {
-
-  private static Map<String, String> mapping = new HashMap<>();
-
-  static {
-    mapping.put("p12", "PKCS12");
-    mapping.put("jceks", "JCEKS");
-    mapping.put("jks", "JKS");
-  }
-
-  /**
-   * Läd und liefert einen KeyStrore.
-   * <p>
-   * Unterstützte Formate (bisher): .p12 (Typ: PKCS12) und .jceks (Typ: JCEKS)
-   *
-   * @param path       der Dateienpfad zur Store-Datei
-   * @param passPhrase Passwort des Stores
-   * @return der durch den Pfad referenzierte KeyStore
-   * @throws ClientInitializationException bei zum Beispiel nicht unterstützen Typen oder wenn die KeyStore-Datei nicht gefunden
-   *                                       wurde
-   */
-  public static KeyStore load(final String path, final String passPhrase) throws ClientInitializationException {
-
-    try {
-      final KeyStore keyStore = KeyStore.getInstance(getType(path));
-      try (final InputStream is = new FileInputStream(path)) {
-        keyStore.load(is, passPhrase.toCharArray());
-      } catch (FileNotFoundException e) {
-        // relativ zum Classpath: Fallback for testing
-        try (final InputStream is = KeyStoreUtil.class.getClassLoader().getResourceAsStream(path)) {
-          keyStore.load(is, passPhrase.toCharArray());
-        }
-      }
-      return keyStore;
-    } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
-      throw new ClientInitializationException("Exception in createSSLContext: " + e.getMessage(), e);
-    }
-  }
-
-  private static String getType(final String path) throws ClientInitializationException {
-    if (!StringUtils.isEmpty(path)) {
-      String key = path.substring(path.lastIndexOf(".") + 1).toUpperCase();
-      return mapping.get(key.toLowerCase());
-    }
-    return KeyStore.getDefaultType();
-  }
-
-  public static SecretKey generateSecretKey() throws NoSuchAlgorithmException {
-    KeyGenerator keygen = KeyGenerator.getInstance("AES");
-    keygen.init(128);
-    return keygen.generateKey();
-  }
-}
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientConfigTestFactory.java b/src/test/java/de/ozgcloud/xta/client/XtaClientConfigTestFactory.java
new file mode 100644
index 0000000..24c8421
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientConfigTestFactory.java
@@ -0,0 +1,99 @@
+package de.ozgcloud.xta.client;
+
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.util.Date;
+import java.util.List;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+import de.ozgcloud.xta.client.config.XtaClientConfig;
+import lombok.SneakyThrows;
+
+public class XtaClientConfigTestFactory {
+
+	static final String SELF_IDENTIFIER = "selfIdentifier";
+	static final String SELF_IDENTIFIER2 = "selfIdentifier2";
+
+	public static final String KEYSTORE_ALIAS = "selfSigned";
+
+	static final String MANAGEMENT_PORT_SVC = "https://../managementPort.svc";
+	static final String SEND_PORT_SVC = "https://../sendPort.svc";
+	static final String MSG_BOX_PORT_SVC = "https://../msgBoxPort.svc";
+
+	public static XtaClientConfig create() {
+		return createBuilder().build();
+	}
+
+	public static XtaClientConfig createWithSelfSignedClientCert() {
+		return createBuilder()
+				.clientCertKeystore(createClientCertKeyStore())
+				.build();
+	}
+
+	public static XtaClientConfig.XtaClientConfigBuilder createBuilder() {
+		return XtaClientConfig.builder()
+				.managementServiceUrl(MANAGEMENT_PORT_SVC)
+				.sendServiceUrl(SEND_PORT_SVC)
+				.msgBoxServiceUrl(MSG_BOX_PORT_SVC)
+				.clientIdentifiers(List.of(SELF_IDENTIFIER, SELF_IDENTIFIER2))
+				.logSoapRequests(true)
+				.logSoapResponses(true)
+				.schemaValidation(true);
+	}
+
+	@SneakyThrows
+	private static XtaClientConfig.KeyStore createClientCertKeyStore() {
+		var password = "password".toCharArray();
+		var type = KeyStore.getDefaultType();
+
+		// Step 1: Create a KeyStore
+		var keyStore = KeyStore.getInstance(type);
+		keyStore.load(null, null); // Initialize KeyStore
+
+		// Step 2: Generate a Key Pair
+		var keyPairGenerator = KeyPairGenerator.getInstance("EC");
+		keyPairGenerator.initialize(384);
+		var keyPair = keyPairGenerator.generateKeyPair();
+
+		// Step 3: Create a Self-Signed Certificate
+		var issuerName = new X500Name("CN=Test, OU=Development, O=MyCompany, L=MyCity, ST=MyState, C=US");
+		var serialNumber = new BigInteger(64, new SecureRandom());
+		var startDate = new Date();
+		var endDate = new Date(startDate.getTime() + 365L * 24L * 60L * 60L * 1000L); // 1 year validity
+
+		var contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(keyPair.getPrivate());
+		var certBuilder = new JcaX509v3CertificateBuilder(
+				issuerName, serialNumber, startDate, endDate, issuerName, keyPair.getPublic());
+
+		var certHolder = certBuilder.build(contentSigner);
+		var cert = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider()).getCertificate(certHolder);
+
+		// Step 4: Store the Key Pair and Certificate in the KeyStore
+		keyStore.setKeyEntry(KEYSTORE_ALIAS, keyPair.getPrivate(), password, new Certificate[] { cert });
+
+		try (var os = new ByteArrayOutputStream()) {
+			keyStore.store(os, password);
+			return XtaClientConfig.KeyStore.builder()
+					.content(os.toByteArray())
+					.type(type)
+					.password(password)
+					.build();
+		}
+	}
+
+	public static XtaClientConfig.KeyStore.KeyStoreBuilder createKeystoreBuilder() {
+		return XtaClientConfig.KeyStore.builder()
+				.content(new byte[] { 0, 1 })
+				.type("AAAA")
+				.password("".toCharArray());
+	}
+}
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java
new file mode 100644
index 0000000..2c29651
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java
@@ -0,0 +1,74 @@
+package de.ozgcloud.xta.client;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+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.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.xta.client.config.XtaClientConfig;
+import de.ozgcloud.xta.client.config.XtaConfigValidator;
+import de.ozgcloud.xta.client.core.XTAServiceFactory;
+import genv3.de.xoev.transport.xta.x211.XTAService;
+import lombok.SneakyThrows;
+
+@ExtendWith(MockitoExtension.class)
+class XtaClientFactoryTest {
+
+	@Mock
+	private XtaConfigValidator configValidator;
+	@Mock
+	private XTAServiceFactory xtaServiceFactory;
+	@Mock
+	private XtaClientConfig config;
+
+	@InjectMocks
+	private XtaClientFactory factory;
+
+	@DisplayName("create")
+	@Nested
+	class TestCreate {
+
+		@Mock
+		private XTAService service;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			when(xtaServiceFactory.create()).thenReturn(service);
+		}
+
+		@DisplayName("should have service")
+		@Test
+		@SneakyThrows
+		void shouldHaveService() {
+			var client = factory.create();
+
+			assertThat(client.getService()).isEqualTo(service);
+		}
+
+		@DisplayName("should have config")
+		@Test
+		@SneakyThrows
+		void shouldHaveConfig() {
+			var client = factory.create();
+
+			assertThat(client.getConfig()).isEqualTo(config);
+		}
+
+		@DisplayName("should call validate")
+		@Test
+		@SneakyThrows
+		void shouldCallValidate() {
+			factory.create();
+
+			verify(configValidator).validate(config);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
index 3fbb317..f61eb01 100644
--- a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java
@@ -2,31 +2,31 @@ package de.ozgcloud.xta.client;
 
 import static org.junit.jupiter.api.Assertions.*;
 
-import java.net.URI;
-import java.util.List;
-
 import org.apache.commons.lang3.NotImplementedException;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
 
-import de.ozgcloud.xta.client.codes.IdentifierType;
 import de.ozgcloud.xta.client.config.XtaClientConfig;
-import de.ozgcloud.xta.client.model.Identifier;
+import genv3.de.xoev.transport.xta.x211.XTAService;
 import genv3.eu.osci.ws.x2014.x10.transport.PartyIdentifierType;
 import genv3.eu.osci.ws.x2014.x10.transport.PartyType;
-import lombok.SneakyThrows;
 
+@ExtendWith(MockitoExtension.class)
 class XtaClientTest {
 
-	final String managementServiceUrl = "https://../managementPort.svc";
-	final String sendServiceUrl = "https://../sendPort.svc";
-	final String msgBoxServiceUrl = "https://../msgBoxPort.svc";
+	@Mock
+	private XTAService service;
+
+	@Mock
+	private XtaClientConfig config;
 
-	final String clientCertKeystore = "certs/client.jceks";
-	final String clientCertKeystorePassword = "*pass*";
-	final String trustCertKeystore = "certs/trust.jceks";
-	final String trustCertKeystorePassword = "*pass*";
+	@InjectMocks
+	private XtaClient client;
 
 	@DisplayName("get messages metadata")
 	@Nested
@@ -38,27 +38,10 @@ class XtaClientTest {
 			var selfIdentifier = new PartyIdentifierType();
 			selfIdentifier.setValue("selfIdentifier");
 			p.setIdentifier(selfIdentifier);
-			var client = createClient();
 
 			assertThrows(NotImplementedException.class, () -> client.getMessagesMetadata("selfIdentifier"));
 		}
 
 	}
 
-	@SneakyThrows
-	private XtaClient createClient() {
-		var identifier1 = new Identifier("identifier1", IdentifierType.XOEV);
-		var config = XtaClientConfig.create(
-				URI.create(managementServiceUrl).toURL(),
-				URI.create(sendServiceUrl).toURL(),
-				null, // URI.create(msgBoxServiceUrl).toURL(), // TODO
-				List.of(identifier1),
-				clientCertKeystore,
-				clientCertKeystorePassword,
-				trustCertKeystore,
-				trustCertKeystorePassword
-		).build();
-		return new XtaClient(config);
-	}
-
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
new file mode 100644
index 0000000..a06f8a6
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java
@@ -0,0 +1,138 @@
+package de.ozgcloud.xta.client.config;
+
+import static java.util.Collections.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.function.UnaryOperator;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.xta.client.XtaClientConfigTestFactory;
+import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import lombok.SneakyThrows;
+
+@ExtendWith(MockitoExtension.class)
+class XtaConfigValidatorTest {
+
+	@InjectMocks
+	private XtaConfigValidator validator;
+
+	@DisplayName("validate")
+	@Nested
+	class TestValidate {
+
+		@DisplayName("should return")
+		@Test
+		@SneakyThrows
+		void shouldReturn() {
+			var config = XtaClientConfigTestFactory.create();
+
+			validator.validate(config);
+		}
+
+		@DisplayName("should return with client keystore")
+		@Test
+		@SneakyThrows
+		void shouldReturnWithClientKeystore() {
+			var config = XtaClientConfigTestFactory.createBuilder()
+					.clientCertKeystore(XtaClientConfigTestFactory.createKeystoreBuilder().build())
+					.build();
+
+			validator.validate(config);
+		}
+
+		@DisplayName("should throw without client identifiers")
+		@Test
+		void shouldThrowWithoutIdentifiers() {
+			var config = XtaClientConfigTestFactory.createBuilder()
+					.clientIdentifiers(emptyList())
+					.build();
+
+			assertThatThrownBy(() -> validator.validate(config))
+					.isInstanceOf(ClientInitializationException.class)
+					.hasMessageContaining("clientIdentifiers");
+		}
+
+		@DisplayName("should throw without management service url")
+		@Test
+		void shouldThrowWithoutManagementServiceUrl() {
+			var config = XtaClientConfigTestFactory.createBuilder()
+					.managementServiceUrl("")
+					.build();
+
+			assertThatThrownBy(() -> validator.validate(config))
+					.isInstanceOf(ClientInitializationException.class)
+					.hasMessageContaining("managementServiceUrl");
+		}
+
+		@DisplayName("should throw without send service url")
+		@Test
+		void shouldThrowWithoutSendServiceUrl() {
+			var config = XtaClientConfigTestFactory.createBuilder()
+					.sendServiceUrl("")
+					.build();
+
+			assertThatThrownBy(() -> validator.validate(config))
+					.isInstanceOf(ClientInitializationException.class)
+					.hasMessageContaining("sendServiceUrl");
+		}
+
+		@DisplayName("should throw without msg box service url")
+		@Test
+		void shouldThrowWithoutMsgBoxServiceUrl() {
+			var config = XtaClientConfigTestFactory.createBuilder()
+					.msgBoxServiceUrl("")
+					.build();
+
+			assertThatThrownBy(() -> validator.validate(config))
+					.isInstanceOf(ClientInitializationException.class)
+					.hasMessageContaining("msgBoxServiceUrl");
+		}
+
+		@DisplayName("with invalid client cert keystore")
+		@Nested
+		class TestWithInvalidClientCertKeystore {
+			@DisplayName("should throw without password")
+			@Test
+			void shouldThrowWithoutKeystorePassword() {
+				var config = createKeystoreWithClientCertKeyStore(o -> o.password(null));
+
+				assertThatThrownBy(() -> validator.validate(config))
+						.isInstanceOf(ClientInitializationException.class)
+						.hasMessageContaining("password");
+			}
+
+			@DisplayName("should throw without type")
+			@Test
+			void shouldThrowWithoutKeystoreType() {
+				var config = createKeystoreWithClientCertKeyStore(o -> o.type(""));
+
+				assertThatThrownBy(() -> validator.validate(config))
+						.isInstanceOf(ClientInitializationException.class)
+						.hasMessageContaining("type");
+			}
+
+			@DisplayName("should throw without content")
+			@Test
+			void shouldThrowWithoutKeystoreContent() {
+				var config = createKeystoreWithClientCertKeyStore(o -> o.content(null));
+
+				assertThatThrownBy(() -> validator.validate(config))
+						.isInstanceOf(ClientInitializationException.class)
+						.hasMessageContaining("content");
+			}
+
+			private XtaClientConfig createKeystoreWithClientCertKeyStore(UnaryOperator<XtaClientConfig.KeyStore.KeyStoreBuilder> operator) {
+				return XtaClientConfigTestFactory.createBuilder()
+						.clientCertKeystore(operator.apply(XtaClientConfigTestFactory.createKeystoreBuilder()).build())
+						.build();
+			}
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/xta/client/core/XTAServiceFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/core/XTAServiceFactoryTest.java
new file mode 100644
index 0000000..6d71e60
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/core/XTAServiceFactoryTest.java
@@ -0,0 +1,377 @@
+package de.ozgcloud.xta.client.core;
+
+import static de.ozgcloud.xta.client.core.XTAServiceFactory.*;
+import static org.assertj.core.api.AssertionsForClassTypes.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.xml.ws.BindingProvider;
+
+import org.apache.cxf.configuration.jsse.TLSClientParameters;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.ext.logging.LoggingInInterceptor;
+import org.apache.cxf.ext.logging.LoggingOutInterceptor;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.transport.http.HTTPConduit;
+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.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.xta.client.XtaClientConfigTestFactory;
+import de.ozgcloud.xta.client.config.XtaClientConfig;
+import genv3.de.xoev.transport.xta.x211.ManagementPortType;
+import genv3.de.xoev.transport.xta.x211.MsgBoxPortType;
+import genv3.de.xoev.transport.xta.x211.SendPortType;
+import genv3.de.xoev.transport.xta.x211.XTAService;
+import lombok.SneakyThrows;
+
+@ExtendWith(MockitoExtension.class)
+class XTAServiceFactoryTest {
+	private final String endpointUrl = "http://endpoint.test/test.svc";
+
+	@Mock
+	private XtaClientConfig config;
+
+	@Mock
+	private XtaTLSClientParametersFactory sslContextFactory;
+
+	@Spy
+	@InjectMocks
+	private XTAServiceFactory factory;
+
+	@DisplayName("create")
+	@Nested
+	class TestCreate {
+
+		@Captor
+		private ArgumentCaptor<XTAService> serviceCaptor;
+
+		@DisplayName("should call configureServiceBindings")
+		@Test
+		@SneakyThrows
+		void shouldCallConfigureServiceBindings() {
+			doNothing().when(factory).configureServiceBindings(any());
+
+			factory.create();
+
+			verify(factory).configureServiceBindings(any());
+		}
+
+		@DisplayName("should return service")
+		@Test
+		@SneakyThrows
+		void shouldReturnService() {
+			doNothing().when(factory).configureServiceBindings(any());
+
+			var result = factory.create();
+
+			verify(factory).configureServiceBindings(serviceCaptor.capture());
+			assertThat(serviceCaptor.getValue()).isNotNull().isEqualTo(result);
+		}
+	}
+
+	@DisplayName("configure service bindings")
+	@Nested
+	class TestConfigureServiceBindings {
+
+		private final XtaClientConfig testConfig = XtaClientConfigTestFactory.create();
+
+		@Mock(extraInterfaces = BindingProvider.class)
+		private ManagementPortType managementPort;
+		@Mock(extraInterfaces = BindingProvider.class)
+		private SendPortType sendXtaPort;
+		@Mock(extraInterfaces = BindingProvider.class)
+		private MsgBoxPortType msgBoxPort;
+
+		@Mock
+		private XTAService service;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			when(config.getManagementServiceUrl()).thenReturn(testConfig.getManagementServiceUrl());
+			when(config.getSendServiceUrl()).thenReturn(testConfig.getSendServiceUrl());
+			when(config.getMsgBoxServiceUrl()).thenReturn(testConfig.getMsgBoxServiceUrl());
+
+			when(service.getManagementPort()).thenReturn(managementPort);
+			when(service.getSendXtaPort()).thenReturn(sendXtaPort);
+			when(service.getMsgBoxPort()).thenReturn(msgBoxPort);
+
+			doNothing().when(factory).configureBinding(any(), any());
+		}
+
+		@DisplayName("should configure management port")
+		@Test
+		@SneakyThrows
+		void shouldConfigureManagementPort() {
+			factory.configureServiceBindings(service);
+
+			verify(factory).configureBinding(testConfig.getManagementServiceUrl(), (BindingProvider) managementPort);
+		}
+
+		@DisplayName("should configure send port")
+		@Test
+		@SneakyThrows
+		void shouldConfigureSendPort() {
+			factory.configureServiceBindings(service);
+
+			verify(factory).configureBinding(testConfig.getSendServiceUrl(), (BindingProvider) sendXtaPort);
+		}
+
+		@DisplayName("should configure msg box port")
+		@Test
+		@SneakyThrows
+		void shouldConfigureMsgBoxPort() {
+			factory.configureServiceBindings(service);
+
+			verify(factory).configureBinding(testConfig.getMsgBoxServiceUrl(), (BindingProvider) msgBoxPort);
+		}
+	}
+
+	@DisplayName("configure binding")
+	@Nested
+	class TestConfigureBinding {
+
+		@Mock
+		private BindingProvider port;
+
+		@Mock
+		private Map<String, Object> requestContext;
+
+		@Mock
+		private Client client;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			when(port.getRequestContext()).thenReturn(requestContext);
+			doNothing().when(factory).configureClient(any());
+			doReturn(client).when(factory).getClientFromPort(port);
+		}
+
+		@DisplayName("should configure request context")
+		@Test
+		@SneakyThrows
+		void shouldConfigureRequestContext() {
+			factory.configureBinding(endpointUrl, port);
+
+			verify(factory).configureRequestContext(endpointUrl, requestContext);
+		}
+
+		@DisplayName("should configure client")
+		@Test
+		@SneakyThrows
+		void shouldConfigureClient() {
+			factory.configureBinding(endpointUrl, port);
+
+			verify(factory).configureClient(client);
+		}
+	}
+
+	@DisplayName("configure request context")
+	@Nested
+	class TestConfigureRequestContext {
+
+		private HashMap<String, Object> requestContext;
+
+		@BeforeEach
+		void mock() {
+			requestContext = new HashMap<>();
+			requestContext.put("old", "value");
+			when(config.isSchemaValidation()).thenReturn(false);
+		}
+
+		@DisplayName("should keep old")
+		@Test
+		void shouldKeepOld() {
+			factory.configureRequestContext(endpointUrl, requestContext);
+
+			assertThat(requestContext.get("old")).isEqualTo("value");
+		}
+
+		@DisplayName("should add endpoint url")
+		@Test
+		@SneakyThrows
+		void shouldAddEndpointUrl() {
+			factory.configureRequestContext(endpointUrl, requestContext);
+
+			assertThat(requestContext.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY)).isEqualTo(endpointUrl);
+		}
+
+		@DisplayName("should add schema validation")
+		@ParameterizedTest
+		@ValueSource(booleans = { true, false })
+		void shouldAddSchemaValidation(boolean validation) {
+			reset(config);
+			when(config.isSchemaValidation()).thenReturn(validation);
+
+			factory.configureRequestContext(endpointUrl, requestContext);
+
+			assertThat(requestContext.get(SCHEMA_VALIDATION_ENABLED_KEY)).isEqualTo(validation);
+		}
+	}
+
+	@DisplayName("configure client")
+	@Nested
+	class TestConfigureClient {
+
+		@Mock
+		private Client client;
+
+		@Mock
+		private List<Interceptor<?>> outInterceptors;
+
+		@Mock
+		private List<Interceptor<?>> inInterceptors;
+
+		@Mock(extraInterfaces = Conduit.class)
+		private HTTPConduit clientConduit;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			when(client.getConduit()).thenReturn(clientConduit);
+			doNothing().when(factory).configureHttpConduit(any());
+		}
+
+		@DisplayName("should configure out interceptors")
+		@Test
+		@SneakyThrows
+		void shouldConfigureOutInterceptors() {
+			when(config.isLogSoapRequests()).thenReturn(true);
+			when(config.isLogSoapResponses()).thenReturn(false);
+			when(client.getOutInterceptors()).thenReturn(outInterceptors);
+			doNothing().when(factory).configureOutInterceptors(any());
+
+			factory.configureClient(client);
+
+			verify(factory).configureOutInterceptors(client.getOutInterceptors());
+		}
+
+		@DisplayName("should configure in interceptors")
+		@Test
+		@SneakyThrows
+		void shouldConfigureInInterceptors() {
+			when(config.isLogSoapRequests()).thenReturn(false);
+			when(config.isLogSoapResponses()).thenReturn(true);
+			when(client.getInInterceptors()).thenReturn(inInterceptors);
+			doNothing().when(factory).configureInInterceptors(any());
+
+			factory.configureClient(client);
+
+			verify(factory).configureInInterceptors(client.getInInterceptors());
+		}
+
+		@DisplayName("should configure http conduit")
+		@Test
+		@SneakyThrows
+		void shouldConfigureHttpConduit() {
+			when(config.isLogSoapRequests()).thenReturn(false);
+			when(config.isLogSoapResponses()).thenReturn(false);
+
+			factory.configureClient(client);
+
+			verify(factory).configureHttpConduit((HTTPConduit) client.getConduit());
+		}
+	}
+
+	@DisplayName("configure in interceptors")
+	@Nested
+	class TestConfigureInInterceptors {
+
+		private List<Interceptor<?>> inInterceptors;
+
+		@BeforeEach
+		void setup() {
+			inInterceptors = new ArrayList<>();
+		}
+
+		@DisplayName("should add one interceptor")
+		@Test
+		void shouldAddOneInterceptor() {
+			factory.configureInInterceptors(inInterceptors);
+
+			assertThat(inInterceptors.size()).isEqualTo(1);
+		}
+
+		@DisplayName("should add in interceptor")
+		@Test
+		@SneakyThrows
+		void shouldAddInInterceptor() {
+			factory.configureInInterceptors(inInterceptors);
+
+			assertThat(inInterceptors.getFirst()).isInstanceOf(LoggingInInterceptor.class);
+		}
+	}
+
+	@DisplayName("configure out interceptors")
+	@Nested
+	class TestConfigureOutInterceptors {
+
+		private List<Interceptor<?>> outInterceptors;
+
+		@BeforeEach
+		void setup() {
+			outInterceptors = new ArrayList<>();
+		}
+
+		@DisplayName("should add one interceptor")
+		@Test
+		void shouldAddOneInterceptor() {
+			factory.configureOutInterceptors(outInterceptors);
+
+			assertThat(outInterceptors.size()).isEqualTo(1);
+		}
+
+		@DisplayName("should add out interceptor")
+		@Test
+		@SneakyThrows
+		void shouldAddOutInterceptor() {
+			factory.configureOutInterceptors(outInterceptors);
+
+			assertThat(outInterceptors.getFirst()).isInstanceOf(LoggingOutInterceptor.class);
+		}
+	}
+
+	@DisplayName("configure http conduit")
+	@Nested
+	class TestConfigureHttpConduit {
+
+		@Mock
+		private HTTPConduit conduit;
+
+		@Mock
+		private TLSClientParameters tlsClientParameters;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			when(sslContextFactory.create()).thenReturn(tlsClientParameters);
+		}
+
+		@DisplayName("should set tls client parameters")
+		@Test
+		@SneakyThrows
+		void shouldSetTlsClientParameters() {
+			factory.configureHttpConduit(conduit);
+
+			verify(conduit).setTlsClientParameters(tlsClientParameters);
+		}
+	}
+
+}
\ 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
new file mode 100644
index 0000000..3e27432
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/core/XtaTLSClientParametersFactoryTest.java
@@ -0,0 +1,192 @@
+package de.ozgcloud.xta.client.core;
+
+import static de.ozgcloud.xta.client.XtaClientConfigTestFactory.*;
+import static de.ozgcloud.xta.client.core.XtaTLSClientParametersFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.hc.core5.ssl.SSLContextBuilder;
+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.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.xta.client.XtaClientConfigTestFactory;
+import de.ozgcloud.xta.client.config.XtaClientConfig;
+import de.ozgcloud.xta.client.exception.ClientInitializationException;
+import lombok.SneakyThrows;
+
+@ExtendWith(MockitoExtension.class)
+class XtaTLSClientParametersFactoryTest {
+
+	@Mock
+	private XtaClientConfig config;
+
+	@InjectMocks
+	@Spy
+	private XtaTLSClientParametersFactory factory;
+
+	@DisplayName("create TLS client parameters")
+	@Nested
+	class TestCreateTlsClientParameters {
+		@Mock
+		SSLSocketFactory sslSocketFactory;
+
+		@Mock
+		SSLContext sslContext;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			doReturn(sslContext).when(factory).createXtaSslContext();
+			when(sslContext.getSocketFactory()).thenReturn(sslSocketFactory);
+		}
+
+		@DisplayName("should have SSL socket factory")
+		@Test
+		@SneakyThrows
+		void shouldHaveSslSocketFactory() {
+			var tlsClientParameters = factory.create();
+
+			assertThat(tlsClientParameters.getSSLSocketFactory()).isEqualTo(sslSocketFactory);
+		}
+	}
+
+	@DisplayName("create XTA SSL context")
+	@Nested
+	class TestCreateXtaSslContext {
+
+		private final char[] PASSWORD = "password".toCharArray();
+
+		@Mock
+		XtaClientConfig.KeyStore configKeystore;
+
+		@Mock
+		KeyStore keystore;
+
+		@Mock
+		SSLContextBuilder sslContextBuilder;
+
+		@Mock
+		SSLContext sslContext;
+
+		@BeforeEach
+		@SneakyThrows
+		void mock() {
+			doReturn(sslContextBuilder).when(factory).createSSLContextBuilder();
+			when(sslContextBuilder.build()).thenReturn(sslContext);
+		}
+
+		@DisplayName("with client key store")
+		@Nested
+		class TestWithClientKeyStore {
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(config.getClientCertKeystore()).thenReturn(configKeystore);
+				when(configKeystore.getPassword()).thenReturn(PASSWORD);
+				doReturn(keystore).when(factory).loadStore(configKeystore);
+			}
+
+			@DisplayName("should call loadKeyMaterial")
+			@Test
+			@SneakyThrows
+			void shouldCallLoadKeyMaterial() {
+				factory.createXtaSslContext();
+
+				verify(sslContextBuilder).loadKeyMaterial(keystore, PASSWORD);
+			}
+
+			@DisplayName("should return SSL context")
+			@Test
+			@SneakyThrows
+			void shouldSetProtocol() {
+				var sslContextResult = factory.createXtaSslContext();
+
+				assertThat(sslContextResult).isEqualTo(sslContext);
+			}
+
+			@DisplayName("should throw client initialization exception for keystore exception")
+			@Test
+			@SneakyThrows
+			void shouldThrowClientInitializationExceptionForKeyStoreException() {
+				reset(sslContextBuilder);
+				doThrow(new KeyStoreException("something")).when(sslContextBuilder).loadKeyMaterial(any(), any());
+
+				assertThatThrownBy(() -> factory.createXtaSslContext())
+						.isInstanceOf(ClientInitializationException.class);
+			}
+		}
+
+		@DisplayName("without client key store")
+		@Nested
+		class TestWithoutClientKeyStore {
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(config.getClientCertKeystore()).thenReturn(null);
+			}
+
+			@DisplayName("should return SSL context")
+			@Test
+			@SneakyThrows
+			void shouldSetProtocol() {
+				var sslContextResult = factory.createXtaSslContext();
+
+				assertThat(sslContextResult).isEqualTo(sslContext);
+			}
+		}
+	}
+
+	@DisplayName("create SSL context builder")
+	@Nested
+	class TestCreateSslContextBuilder {
+
+		@DisplayName("should set protocol")
+		@Test
+		@SneakyThrows
+		void shouldSetProtocol() {
+			var sslContext = factory.createSSLContextBuilder().build();
+
+			assertThat(sslContext.getProtocol()).isEqualTo(TLS_PROTOCOL);
+		}
+	}
+
+	@DisplayName("load store")
+	@Nested
+	class TestLoadStore {
+
+		private final XtaClientConfig.KeyStore configKeyStore = XtaClientConfigTestFactory.createWithSelfSignedClientCert().getClientCertKeystore();
+
+		@DisplayName("should have type")
+		@Test
+		@SneakyThrows
+		void shouldHaveType() {
+			var keystore = factory.loadStore(configKeyStore);
+
+			assertThat(keystore.getType()).isEqualTo(configKeyStore.getType());
+		}
+
+		@DisplayName("should have entry")
+		@Test
+		@SneakyThrows
+		void shouldHaveEntry() {
+			var keystore = factory.loadStore(configKeyStore);
+
+			assertThat(keystore.isKeyEntry(KEYSTORE_ALIAS)).isTrue();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000..b059a65
--- /dev/null
+++ b/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
\ No newline at end of file
diff --git a/src/test/resources/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 0000000..79b126e
--- /dev/null
+++ b/src/test/resources/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+org.mockito.junit.jupiter.MockitoExtension
\ No newline at end of file
-- 
GitLab