diff --git a/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java b/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java index d41b6f7d57..9ee3dd0cc2 100755 --- a/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java +++ b/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java @@ -384,11 +384,6 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, getFilter().ifPresent(theFilter -> jsonObjectBuilder.set(JsonFields.FILTER, theFilter)); } - @Override - public String getTypePrefix() { - return TYPE_PREFIX; - } - @Override public SubscribeForPersistedEvents setDittoHeaders(final DittoHeaders dittoHeaders) { return new SubscribeForPersistedEvents(entityId, resourcePath, fromHistoricalRevision, toHistoricalRevision, diff --git a/base/service/pom.xml b/base/service/pom.xml index 566bbb9f20..4428d44269 100644 --- a/base/service/pom.xml +++ b/base/service/pom.xml @@ -38,6 +38,10 @@ org.eclipse.ditto ditto-internal-utils-metrics + + org.eclipse.ditto + ditto-internal-utils-metrics-service + org.eclipse.ditto ditto-internal-utils-tracing diff --git a/base/service/src/main/java/org/eclipse/ditto/base/service/DittoService.java b/base/service/src/main/java/org/eclipse/ditto/base/service/DittoService.java index 2dd6e3e368..01f8ea9d1f 100644 --- a/base/service/src/main/java/org/eclipse/ditto/base/service/DittoService.java +++ b/base/service/src/main/java/org/eclipse/ditto/base/service/DittoService.java @@ -47,7 +47,7 @@ import org.eclipse.ditto.internal.utils.config.raw.RawConfigSupplier; import org.eclipse.ditto.internal.utils.health.status.StatusSupplierActor; import org.eclipse.ditto.internal.utils.metrics.config.MetricsConfig; -import org.eclipse.ditto.internal.utils.metrics.prometheus.PrometheusReporterRoute; +import org.eclipse.ditto.internal.utils.metrics.service.prometheus.PrometheusReporterRoute; import org.eclipse.ditto.internal.utils.tracing.DittoTracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bom/pom.xml b/bom/pom.xml index 6013fc2438..df42a33dc0 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -27,7 +27,7 @@ 2.13 - 2.13.12 + 2.13.14 1.1.2 1.0.2 @@ -37,6 +37,7 @@ 0.9.5 2.16.1 + 1.4.0 1.4.3 0.6.1 3.6.1 @@ -75,7 +76,7 @@ 3.1.11 - 2.7.0 + 2.7.1 3.0.2 @@ -130,6 +131,22 @@ import + + com.networknt + json-schema-validator + ${json-schema-validator.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + org.apache.commons + commons-lang3 + + + + com.typesafe config @@ -538,11 +555,21 @@ ditto-rql-query ${project.version} + + org.eclipse.ditto + ditto-wot-api + ${project.version} + org.eclipse.ditto ditto-wot-model ${project.version} + + org.eclipse.ditto + ditto-wot-validation + ${project.version} + org.eclipse.ditto ditto-wot-integration @@ -616,6 +643,11 @@ ditto-internal-utils-http ${project.version} + + org.eclipse.ditto + ditto-internal-utils-json + ${project.version} + org.eclipse.ditto ditto-internal-utils-jwt @@ -666,6 +698,11 @@ ditto-internal-utils-metrics ${project.version} + + org.eclipse.ditto + ditto-internal-utils-metrics-service + ${project.version} + org.eclipse.ditto ditto-internal-utils-extension diff --git a/connectivity/service/pom.xml b/connectivity/service/pom.xml index fe4984a3cf..afd3837629 100644 --- a/connectivity/service/pom.xml +++ b/connectivity/service/pom.xml @@ -14,12 +14,12 @@ + 4.0.0 ditto-connectivity org.eclipse.ditto ${revision} - 4.0.0 ditto-connectivity-service Eclipse Ditto :: Connectivity :: Service @@ -55,6 +55,10 @@ org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto @@ -69,6 +73,10 @@ org.eclipse.ditto ditto-internal-models-signalenrichment + + org.eclipse.ditto + ditto-internal-utils-http + org.eclipse.ditto ditto-internal-utils-persistence diff --git a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfig.java b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfig.java index 89e75ba647..334ec3fa14 100644 --- a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfig.java +++ b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfig.java @@ -19,10 +19,10 @@ import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.internal.utils.http.config.DefaultHttpProxyConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import com.typesafe.config.Config; diff --git a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/HttpPushConfig.java b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/HttpPushConfig.java index 35cd538632..06eaa7b7cf 100644 --- a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/HttpPushConfig.java +++ b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/config/HttpPushConfig.java @@ -16,8 +16,8 @@ import java.util.List; import java.util.Map; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.config.KnownConfigValue; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import com.typesafe.config.Config; diff --git a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushClientActor.java b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushClientActor.java index 36ca0bd024..c22a436a70 100644 --- a/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushClientActor.java +++ b/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushClientActor.java @@ -39,7 +39,6 @@ import org.apache.pekko.stream.javadsl.Sink; import org.apache.pekko.stream.javadsl.Source; import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.connectivity.model.Connection; import org.eclipse.ditto.connectivity.model.signals.commands.modify.TestConnection; import org.eclipse.ditto.connectivity.service.config.HttpPushConfig; @@ -50,6 +49,7 @@ import org.eclipse.ditto.connectivity.service.messaging.internal.ssl.SSLContextCreator; import org.eclipse.ditto.connectivity.service.messaging.monitoring.ConnectionMonitor; import org.eclipse.ditto.connectivity.service.messaging.monitoring.logs.InfoProviderFactory; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; import com.typesafe.config.Config; @@ -215,7 +215,7 @@ private CompletionStage testSSL(final Connection connection, fina } private CompletionStage connectViaProxy(final String hostWithoutLookup, final int port) { - final HttpProxyConfig httpProxyConfig = this.httpPushConfig.getHttpProxyConfig(); + final HttpProxyBaseConfig httpProxyConfig = this.httpPushConfig.getHttpProxyConfig(); try (final Socket proxySocket = new Socket(httpProxyConfig.getHostname(), httpProxyConfig.getPort())) { String proxyConnect = "CONNECT " + hostWithoutLookup + ":" + port + " HTTP/1.1\n"; proxyConnect += "Host: " + hostWithoutLookup + ":" + port; diff --git a/connectivity/service/src/main/resources/connectivity.conf b/connectivity/service/src/main/resources/connectivity.conf index 411aa82270..ee3434b22a 100644 --- a/connectivity/service/src/main/resources/connectivity.conf +++ b/connectivity/service/src/main/resources/connectivity.conf @@ -1272,7 +1272,7 @@ pekko-contrib-mongodb-persistence-connection-remember-snapshots { connection-persistence-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" fork-join-executor { parallelism-min = 4 parallelism-factor = 3.0 @@ -1293,7 +1293,7 @@ rabbit-stats-bounded-mailbox { message-mapping-processor-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" fork-join-executor { # Min number of threads to cap factor-based parallelism number to parallelism-min = 4 @@ -1308,17 +1308,17 @@ message-mapping-processor-dispatcher { jms-connection-handling-dispatcher { # one thread per actor because the actor blocks. type = PinnedDispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } signal-enrichment-cache-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } http-push-connection-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" # This executor is meant to be allowed to grow quite big as its limited by the max parallelism of each http connection client. # Limit this parallelism here additionally could lead to confusing results regarding througput of some http connections. @@ -1332,17 +1332,17 @@ http-push-connection-dispatcher { kafka-consumer-dispatcher { type = PinnedDispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } kafka-producer-dispatcher { type = PinnedDispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } blocked-namespaces-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" fork-join-executor { # Min number of threads to cap factor-based parallelism number to parallelism-min = 4 diff --git a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/ConnectivityServiceGlobalErrorRegistryTest.java b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/ConnectivityServiceGlobalErrorRegistryTest.java index a0eaf38f37..4f71b56324 100644 --- a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/ConnectivityServiceGlobalErrorRegistryTest.java +++ b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/ConnectivityServiceGlobalErrorRegistryTest.java @@ -45,6 +45,7 @@ import org.eclipse.ditto.thingsearch.api.QueryTimeExceededException; import org.eclipse.ditto.thingsearch.model.signals.commands.exceptions.InvalidNamespacesException; import org.eclipse.ditto.wot.model.WotThingModelInvalidException; +import org.eclipse.ditto.wot.validation.WotThingModelPayloadValidationException; public final class ConnectivityServiceGlobalErrorRegistryTest extends GlobalErrorRegistryTestCases { @@ -81,7 +82,8 @@ public ConnectivityServiceGlobalErrorRegistryTest() { JwtInvalidException.class, IllegalAdaptableException.class, WotThingModelInvalidException.class, - EdgeServiceTimeoutException.class + EdgeServiceTimeoutException.class, + WotThingModelPayloadValidationException.class ); } diff --git a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfigTest.java b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfigTest.java index 6880e52011..7db14ee2cc 100644 --- a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfigTest.java +++ b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/config/DefaultHttpPushConfigTest.java @@ -21,7 +21,7 @@ import java.util.Map; import org.assertj.core.api.JUnitSoftAssertions; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; diff --git a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushFactoryTest.java b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushFactoryTest.java index 7517bda751..a7c37775fe 100644 --- a/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushFactoryTest.java +++ b/connectivity/service/src/test/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpPushFactoryTest.java @@ -30,9 +30,27 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.apache.pekko.NotUsed; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.http.impl.engine.client.ProxyConnectionFailedException; +import org.apache.pekko.http.javadsl.Http; +import org.apache.pekko.http.javadsl.ServerBinding; +import org.apache.pekko.http.javadsl.model.HttpMethods; +import org.apache.pekko.http.javadsl.model.HttpRequest; +import org.apache.pekko.http.javadsl.model.HttpResponse; +import org.apache.pekko.http.javadsl.model.StatusCodes; +import org.apache.pekko.http.javadsl.model.headers.Authorization; +import org.apache.pekko.japi.Pair; +import org.apache.pekko.stream.KillSwitches; +import org.apache.pekko.stream.OverflowStrategy; +import org.apache.pekko.stream.javadsl.Flow; +import org.apache.pekko.stream.javadsl.Keep; +import org.apache.pekko.stream.javadsl.Sink; +import org.apache.pekko.stream.javadsl.SinkQueueWithCancel; +import org.apache.pekko.stream.javadsl.Source; +import org.apache.pekko.stream.javadsl.SourceQueueWithComplete; +import org.apache.pekko.testkit.javadsl.TestKit; import org.eclipse.ditto.base.service.config.DittoServiceConfig; -import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.connectivity.model.Connection; import org.eclipse.ditto.connectivity.model.ConnectionType; import org.eclipse.ditto.connectivity.model.ConnectivityModelFactory; @@ -47,6 +65,8 @@ import org.eclipse.ditto.connectivity.service.messaging.monitoring.logs.InfoProviderFactory; import org.eclipse.ditto.connectivity.service.messaging.tunnel.SshTunnelState; import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig; +import org.eclipse.ditto.internal.utils.http.config.DefaultHttpProxyConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -54,26 +74,6 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import org.apache.pekko.NotUsed; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.http.impl.engine.client.ProxyConnectionFailedException; -import org.apache.pekko.http.javadsl.Http; -import org.apache.pekko.http.javadsl.ServerBinding; -import org.apache.pekko.http.javadsl.model.HttpMethods; -import org.apache.pekko.http.javadsl.model.HttpRequest; -import org.apache.pekko.http.javadsl.model.HttpResponse; -import org.apache.pekko.http.javadsl.model.StatusCodes; -import org.apache.pekko.http.javadsl.model.headers.Authorization; -import org.apache.pekko.japi.Pair; -import org.apache.pekko.stream.KillSwitches; -import org.apache.pekko.stream.OverflowStrategy; -import org.apache.pekko.stream.javadsl.Flow; -import org.apache.pekko.stream.javadsl.Keep; -import org.apache.pekko.stream.javadsl.Sink; -import org.apache.pekko.stream.javadsl.SinkQueueWithCancel; -import org.apache.pekko.stream.javadsl.Source; -import org.apache.pekko.stream.javadsl.SourceQueueWithComplete; -import org.apache.pekko.testkit.javadsl.TestKit; import scala.util.Failure; import scala.util.Try; diff --git a/gateway/service/pom.xml b/gateway/service/pom.xml index bdb9d68809..3bd8277820 100644 --- a/gateway/service/pom.xml +++ b/gateway/service/pom.xml @@ -101,6 +101,10 @@ org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto ditto-internal-models-signalenrichment diff --git a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/AuthenticationConfig.java b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/AuthenticationConfig.java index 7ce4458a28..41383048f2 100644 --- a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/AuthenticationConfig.java +++ b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/AuthenticationConfig.java @@ -14,8 +14,8 @@ import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.config.KnownConfigValue; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; /** * Provides configuration settings for the Gateway authentication. diff --git a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfig.java b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfig.java index ccf2840723..226e850343 100644 --- a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfig.java +++ b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfig.java @@ -17,11 +17,11 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; import org.eclipse.ditto.internal.utils.config.ScopedConfig; import org.eclipse.ditto.internal.utils.config.WithConfigPath; +import org.eclipse.ditto.internal.utils.http.config.DefaultHttpProxyConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import com.typesafe.config.Config; diff --git a/gateway/service/src/main/resources/gateway.conf b/gateway/service/src/main/resources/gateway.conf index 534454aeef..552cd1684d 100755 --- a/gateway/service/src/main/resources/gateway.conf +++ b/gateway/service/src/main/resources/gateway.conf @@ -475,7 +475,7 @@ pekko.http.client { pekko { actor { default-dispatcher { - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" } deployment { /gatewayRoot/proxy { @@ -604,7 +604,7 @@ include "ditto-edge-service.conf" authentication-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" thread-pool-executor { # minimum number of threads to cap factor-based core number to core-pool-size-min = 4 @@ -620,7 +620,7 @@ authentication-dispatcher { signal-enrichment-cache-dispatcher { type = Dispatcher - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } include "gateway-extension.conf" diff --git a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/EndpointTestBase.java b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/EndpointTestBase.java index 10a5aee5cf..a00206bcac 100755 --- a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/EndpointTestBase.java +++ b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/EndpointTestBase.java @@ -47,7 +47,6 @@ import org.eclipse.ditto.base.model.signals.commands.AbstractCommandResponse; import org.eclipse.ditto.base.model.signals.commands.Command; import org.eclipse.ditto.base.model.signals.commands.CommandResponse; -import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig; import org.eclipse.ditto.gateway.service.endpoints.routes.RootRouteExceptionHandler; import org.eclipse.ditto.gateway.service.endpoints.routes.RouteBaseProperties; import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationFactory; @@ -77,6 +76,7 @@ import org.eclipse.ditto.internal.utils.health.cluster.ClusterStatus; import org.eclipse.ditto.internal.utils.http.DefaultHttpClientFacade; import org.eclipse.ditto.internal.utils.http.HttpClientFacade; +import org.eclipse.ditto.internal.utils.http.config.DefaultHttpProxyConfig; import org.eclipse.ditto.internal.utils.protocol.ProtocolAdapterProvider; import org.eclipse.ditto.internal.utils.protocol.config.DefaultProtocolConfig; import org.eclipse.ditto.internal.utils.protocol.config.ProtocolConfig; diff --git a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/starter/GatewayServiceGlobalErrorRegistryTest.java b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/starter/GatewayServiceGlobalErrorRegistryTest.java index a1f61db9ea..939a85a15e 100644 --- a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/starter/GatewayServiceGlobalErrorRegistryTest.java +++ b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/starter/GatewayServiceGlobalErrorRegistryTest.java @@ -45,6 +45,7 @@ import org.eclipse.ditto.thingsearch.api.QueryTimeExceededException; import org.eclipse.ditto.thingsearch.model.signals.commands.exceptions.InvalidNamespacesException; import org.eclipse.ditto.wot.model.WotThingModelInvalidException; +import org.eclipse.ditto.wot.validation.WotThingModelPayloadValidationException; public final class GatewayServiceGlobalErrorRegistryTest extends GlobalErrorRegistryTestCases { @@ -81,7 +82,8 @@ public GatewayServiceGlobalErrorRegistryTest() { UnknownTopicPathException.class, IllegalAdaptableException.class, WotThingModelInvalidException.class, - EdgeServiceTimeoutException.class + EdgeServiceTimeoutException.class, + WotThingModelPayloadValidationException.class ); } diff --git a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfigTest.java b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfigTest.java index 0586b53781..de08037dae 100644 --- a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfigTest.java +++ b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/util/config/security/DefaultAuthenticationConfigTest.java @@ -17,7 +17,7 @@ import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; import org.assertj.core.api.JUnitSoftAssertions; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; diff --git a/internal/utils/cache-loaders/pom.xml b/internal/utils/cache-loaders/pom.xml index ccca669843..866f78907f 100644 --- a/internal/utils/cache-loaders/pom.xml +++ b/internal/utils/cache-loaders/pom.xml @@ -49,6 +49,11 @@ logback-classic test + + org.apache.pekko + pekko-slf4j_${scala.version} + test + org.apache.pekko pekko-testkit_${scala.version} diff --git a/internal/utils/cache-loaders/src/main/resources/reference.conf b/internal/utils/cache-loaders/src/main/resources/reference.conf index 822e734be2..6d2978e214 100644 --- a/internal/utils/cache-loaders/src/main/resources/reference.conf +++ b/internal/utils/cache-loaders/src/main/resources/reference.conf @@ -3,5 +3,5 @@ ask-with-retry-dispatcher { type = "Dispatcher" - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator" } diff --git a/internal/utils/cluster/pom.xml b/internal/utils/cluster/pom.xml index ff46868b7b..ae29b2ab89 100755 --- a/internal/utils/cluster/pom.xml +++ b/internal/utils/cluster/pom.xml @@ -47,6 +47,10 @@ org.eclipse.ditto ditto-internal-utils-health + + org.eclipse.ditto + ditto-internal-utils-json + org.eclipse.ditto ditto-internal-utils-metrics diff --git a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializer.java b/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializer.java index 49486887ff..c92143b325 100644 --- a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializer.java +++ b/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializer.java @@ -21,6 +21,13 @@ import java.text.MessageFormat; import java.util.Map; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.ExtendedActorSystem; +import org.apache.pekko.io.BufferPool; +import org.apache.pekko.io.DirectByteBufferPool; +import org.apache.pekko.serialization.ByteBufferSerializer; +import org.apache.pekko.serialization.SerializerWithStringManifest; +import org.eclipse.ditto.internal.utils.json.CborFactoryLoader; import org.eclipse.ditto.internal.utils.metrics.DittoMetrics; import org.eclipse.ditto.internal.utils.metrics.instruments.counter.Counter; import org.eclipse.ditto.internal.utils.metrics.instruments.tag.Tag; @@ -36,13 +43,6 @@ import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.ExtendedActorSystem; -import org.apache.pekko.io.BufferPool; -import org.apache.pekko.io.DirectByteBufferPool; -import org.apache.pekko.serialization.ByteBufferSerializer; -import org.apache.pekko.serialization.SerializerWithStringManifest; - /** * Serializer of Eclipse Ditto for {@link JsonValue}s via CBOR. */ diff --git a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonifiableSerializer.java b/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonifiableSerializer.java index f78ddaa116..7a6e931c95 100644 --- a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonifiableSerializer.java +++ b/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborJsonifiableSerializer.java @@ -15,12 +15,12 @@ import java.io.IOException; import java.nio.ByteBuffer; +import org.apache.pekko.actor.ExtendedActorSystem; +import org.eclipse.ditto.internal.utils.json.CborFactoryLoader; import org.eclipse.ditto.json.CborFactory; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonValue; -import org.apache.pekko.actor.ExtendedActorSystem; - /** * Serializer of Eclipse Ditto for Jsonifiables via CBOR-based {@code ditto-json}. */ diff --git a/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializerTest.java b/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializerTest.java index 30ad3015a3..3c7d92dce6 100644 --- a/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializerTest.java +++ b/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborJsonValueSerializerTest.java @@ -21,6 +21,10 @@ import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.ExtendedActorSystem; +import org.apache.pekko.testkit.TestKit; +import org.eclipse.ditto.internal.utils.json.CborFactoryLoader; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonValue; import org.junit.AfterClass; @@ -28,9 +32,6 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.ExtendedActorSystem; -import org.apache.pekko.testkit.TestKit; import scala.concurrent.duration.FiniteDuration; /** diff --git a/internal/utils/config/pom.xml b/internal/utils/config/pom.xml index aebbdf6b16..02078c2148 100755 --- a/internal/utils/config/pom.xml +++ b/internal/utils/config/pom.xml @@ -46,18 +46,6 @@ org.slf4j slf4j-api - - org.apache.pekko - pekko-actor_${scala.version} - - - org.apache.pekko - pekko-lease-kubernetes_${scala.version} - - - org.apache.pekko - pekko-coordination_${scala.version} - diff --git a/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java new file mode 100644 index 0000000000..5ee63c22a2 --- /dev/null +++ b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.config.http; + +import java.util.Objects; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the HTTP proxy config. + */ +@Immutable +public final class DefaultHttpProxyBaseConfig implements HttpProxyBaseConfig { + + private static final String HTTP_PROXY_PATH = "http.proxy"; + private static final String PROXY_PATH = "proxy"; + + private final boolean enabled; + private final String hostName; + private final int port; + private final String userName; + private final String password; + + private DefaultHttpProxyBaseConfig(final ConfigWithFallback configWithFallback) { + enabled = configWithFallback.getBoolean(HttpProxyConfigValue.ENABLED.getConfigPath()); + hostName = configWithFallback.getString(HttpProxyConfigValue.HOST_NAME.getConfigPath()); + port = configWithFallback.getInt(HttpProxyConfigValue.PORT.getConfigPath()); + userName = configWithFallback.getString(HttpProxyConfigValue.USER_NAME.getConfigPath()); + password = configWithFallback.getString(HttpProxyConfigValue.PASSWORD.getConfigPath()); + } + + /** + * Returns an instance of {@code DefaultHttpProxyConfig} based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the HTTP proxy config at {@value #HTTP_PROXY_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultHttpProxyBaseConfig ofHttpProxy(final Config config) { + return ofConfigPath(config, HTTP_PROXY_PATH); + } + + public static DefaultHttpProxyBaseConfig ofProxy(final Config config) { + return ofConfigPath(config, PROXY_PATH); + } + + private static DefaultHttpProxyBaseConfig ofConfigPath(final Config config, final String relativePath) { + return new DefaultHttpProxyBaseConfig( + ConfigWithFallback.newInstance(config, relativePath, HttpProxyConfigValue.values())); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public String getHostname() { + return hostName; + } + + @Override + public int getPort() { + return port; + } + + @Override + public String getUsername() { + return userName; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultHttpProxyBaseConfig that = (DefaultHttpProxyBaseConfig) o; + return enabled == that.enabled && + port == that.port && + Objects.equals(hostName, that.hostName) && + Objects.equals(userName, that.userName) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, hostName, port, userName, password); + } + + @SuppressWarnings("squid:S2068") + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enabled=" + enabled + + ", hostName=" + hostName + + ", port=" + port + + ", userName=" + userName + + ", password=*****" + + "]"; + } + +} diff --git a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java similarity index 83% rename from base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java rename to internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java index 8a67baed53..93af767e59 100644 --- a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java +++ b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,19 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.base.service.config.http; +package org.eclipse.ditto.internal.utils.config.http; import javax.annotation.concurrent.Immutable; import org.eclipse.ditto.internal.utils.config.KnownConfigValue; -import org.apache.pekko.http.javadsl.ClientTransport; - /** * Provides configuration settings for the HTTP proxy. */ @Immutable -public interface HttpProxyConfig { +public interface HttpProxyBaseConfig { /** * Indicates whether the HTTP proxy should be enabled. @@ -59,14 +57,6 @@ public interface HttpProxyConfig { */ String getPassword(); - /** - * Converts the proxy settings to an Pekko HTTP client transport object. - * Does not check whether the proxy is enabled. - * - * @return an Pekko HTTP client transport object matching this config. - */ - ClientTransport toClientTransport(); - /** * An enumeration of the known config path expressions and their associated default values for * {@code HttpProxyConfig}. diff --git a/internal/utils/config/src/main/resources/ditto-pekko-config.conf b/internal/utils/config/src/main/resources/ditto-pekko-config.conf index f32d27946f..0174e5d62d 100644 --- a/internal/utils/config/src/main/resources/ditto-pekko-config.conf +++ b/internal/utils/config/src/main/resources/ditto-pekko-config.conf @@ -113,7 +113,7 @@ pekko { } default-dispatcher { - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" fork-join-executor { parallelism-min = 4 parallelism-factor = 3.0 @@ -277,7 +277,7 @@ sharding-dispatcher { # Dispatcher is the name of the event-based dispatcher type = Dispatcher # What kind of ExecutionService to use - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" # Configuration for the fork join pool fork-join-executor { # Min number of threads to cap factor-based parallelism number to @@ -316,7 +316,7 @@ pekko.contrib.persistence.mongodb.mongo { realtime-enable-persistence = false metrics-builder { - class = "org.eclipse.ditto.internal.utils.metrics.mongo.MongoMetricsBuilder" + class = "org.eclipse.ditto.internal.utils.metrics.service.mongo.MongoMetricsBuilder" class = ${?MONGO_METRICS_BUILDER_CLASS} } } diff --git a/internal/utils/config/src/main/resources/ditto-things-aggregator.conf b/internal/utils/config/src/main/resources/ditto-things-aggregator.conf index 8465237434..be5714d3c7 100644 --- a/internal/utils/config/src/main/resources/ditto-things-aggregator.conf +++ b/internal/utils/config/src/main/resources/ditto-things-aggregator.conf @@ -11,7 +11,7 @@ aggregator-internal-dispatcher { # Dispatcher is the name of the event-based dispatcher type = Dispatcher # What kind of ExecutionService to use - executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator" + executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator" # Configuration for the fork join pool fork-join-executor { # Min number of threads to cap factor-based parallelism number to diff --git a/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java b/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java new file mode 100644 index 0000000000..c34a4c0590 --- /dev/null +++ b/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.config.http; + +import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; +import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; + +import org.assertj.core.api.JUnitSoftAssertions; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import junit.framework.TestCase; +import nl.jqno.equalsverifier.EqualsVerifier; + +public class DefaultHttpProxyBaseConfigTest extends TestCase { + + private static Config httpProxyConfig; + + @Rule + public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); + + @BeforeClass + public static void initTestFixture() { + httpProxyConfig = ConfigFactory.load("http-proxy-test"); + } + + @Test + public void assertImmutability() { + assertInstancesOf(DefaultHttpProxyBaseConfig.class, areImmutable()); + } + + @Test + public void testHashCodeAndEquals() { + EqualsVerifier.forClass(DefaultHttpProxyBaseConfig.class) + .usingGetClass() + .verify(); + } + + @Test + public void underTestReturnsDefaultValuesIfBaseConfigWasEmpty() { + final DefaultHttpProxyBaseConfig underTest = DefaultHttpProxyBaseConfig.ofHttpProxy(ConfigFactory.empty()); + + softly.assertThat(underTest.isEnabled()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getDefaultValue()); + softly.assertThat(underTest.getHostname()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getDefaultValue()); + softly.assertThat(underTest.getPort()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getDefaultValue()); + softly.assertThat(underTest.getUsername()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getDefaultValue()); + softly.assertThat(underTest.getPassword()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getDefaultValue()); + } + + @Test + public void underTestReturnsValuesOfConfigFile() { + final DefaultHttpProxyBaseConfig underTest = DefaultHttpProxyBaseConfig.ofHttpProxy(httpProxyConfig); + + softly.assertThat(underTest.isEnabled()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isTrue(); + softly.assertThat(underTest.getHostname()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo("example.com"); + softly.assertThat(underTest.getPort()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(4711); + softly.assertThat(underTest.getUsername()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo("john.frume"); + softly.assertThat(underTest.getPassword()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo("verySecretPW!"); + } +} \ No newline at end of file diff --git a/base/service/src/test/resources/http-proxy-test.conf b/internal/utils/config/src/test/resources/http-proxy-test.conf similarity index 100% rename from base/service/src/test/resources/http-proxy-test.conf rename to internal/utils/config/src/test/resources/http-proxy-test.conf diff --git a/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java index b0719d9787..7b5bccea7e 100644 --- a/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java +++ b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java @@ -16,13 +16,13 @@ import javax.annotation.Nullable; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; - import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.http.javadsl.Http; import org.apache.pekko.http.javadsl.model.HttpRequest; import org.apache.pekko.http.javadsl.model.HttpResponse; import org.apache.pekko.http.javadsl.settings.ConnectionPoolSettings; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; +import org.eclipse.ditto.internal.utils.http.config.HttpProxyConfig; /** * Default implementation of {@link HttpClientFacade}. @@ -49,7 +49,7 @@ private DefaultHttpClientFacade(final ActorSystem actorSystem, * @return the instance. */ public static DefaultHttpClientFacade getInstance(final ActorSystem actorSystem, - final HttpProxyConfig httpProxyConfig) { + final HttpProxyBaseConfig httpProxyConfig) { // the HttpClientProvider is only configured at the very first invocation of getInstance(Config) as we can // assume that the config does not change during runtime @@ -62,10 +62,10 @@ public static DefaultHttpClientFacade getInstance(final ActorSystem actorSystem, } private static DefaultHttpClientFacade createInstance(final ActorSystem actorSystem, - final HttpProxyConfig proxyConfig) { + final HttpProxyBaseConfig proxyConfig) { ConnectionPoolSettings connectionPoolSettings = ConnectionPoolSettings.create(actorSystem); - if (proxyConfig.isEnabled()) { - connectionPoolSettings = connectionPoolSettings.withTransport(proxyConfig.toClientTransport()); + if (proxyConfig.isEnabled() && proxyConfig instanceof HttpProxyConfig pekkoHttpProxyConfig) { + connectionPoolSettings = connectionPoolSettings.withTransport(pekkoHttpProxyConfig.toClientTransport()); } return new DefaultHttpClientFacade(actorSystem, connectionPoolSettings); } diff --git a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfig.java b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfig.java similarity index 97% rename from base/service/src/main/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfig.java rename to internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfig.java index 1045897c1d..11bdd23281 100644 --- a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfig.java +++ b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.base.service.config.http; +package org.eclipse.ditto.internal.utils.http.config; import java.net.InetSocketAddress; import java.util.Objects; @@ -18,14 +18,13 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.http.javadsl.ClientTransport; +import org.apache.pekko.http.javadsl.model.headers.HttpCredentials; import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; import org.eclipse.ditto.internal.utils.config.DittoConfigError; import com.typesafe.config.Config; -import org.apache.pekko.http.javadsl.ClientTransport; -import org.apache.pekko.http.javadsl.model.headers.HttpCredentials; - /** * This class is the default implementation of the HTTP proxy config. */ diff --git a/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/HttpProxyConfig.java b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/HttpProxyConfig.java new file mode 100644 index 0000000000..30eb3d7ff7 --- /dev/null +++ b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/HttpProxyConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.http.config; + +import javax.annotation.concurrent.Immutable; + +import org.apache.pekko.http.javadsl.ClientTransport; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; + +/** + * Provides configuration settings for the HTTP proxy with additional Pekko HTTP specifics. + */ +@Immutable +public interface HttpProxyConfig extends HttpProxyBaseConfig { + + /** + * Converts the proxy settings to a Pekko HTTP client transport object. + * Does not check whether the proxy is enabled. + * + * @return a Pekko HTTP client transport object matching this config. + */ + ClientTransport toClientTransport(); + +} diff --git a/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java b/internal/utils/http/src/test/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfigTest.java similarity index 62% rename from base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java rename to internal/utils/http/src/test/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfigTest.java index ee956c4997..de17c920b4 100644 --- a/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java +++ b/internal/utils/http/src/test/java/org/eclipse/ditto/internal/utils/http/config/DefaultHttpProxyConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.base.service.config.http; +package org.eclipse.ditto.internal.utils.http.config; import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; import org.assertj.core.api.JUnitSoftAssertions; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig.HttpProxyConfigValue; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -58,20 +58,20 @@ public void underTestReturnsDefaultValuesIfBaseConfigWasEmpty() { final DefaultHttpProxyConfig underTest = DefaultHttpProxyConfig.ofHttpProxy(ConfigFactory.empty()); softly.assertThat(underTest.isEnabled()) - .as(HttpProxyConfigValue.ENABLED.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.ENABLED.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getDefaultValue()); softly.assertThat(underTest.getHostname()) - .as(HttpProxyConfigValue.HOST_NAME.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.HOST_NAME.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getDefaultValue()); softly.assertThat(underTest.getPort()) - .as(HttpProxyConfigValue.PORT.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.PORT.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getDefaultValue()); softly.assertThat(underTest.getUsername()) - .as(HttpProxyConfigValue.USER_NAME.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.USER_NAME.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getDefaultValue()); softly.assertThat(underTest.getPassword()) - .as(HttpProxyConfigValue.PASSWORD.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.PASSWORD.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getDefaultValue()); } @Test @@ -79,19 +79,19 @@ public void underTestReturnsValuesOfConfigFile() { final DefaultHttpProxyConfig underTest = DefaultHttpProxyConfig.ofHttpProxy(httpProxyConfig); softly.assertThat(underTest.isEnabled()) - .as(HttpProxyConfigValue.ENABLED.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) .isTrue(); softly.assertThat(underTest.getHostname()) - .as(HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) .isEqualTo("example.com"); softly.assertThat(underTest.getPort()) - .as(HttpProxyConfigValue.PORT.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) .isEqualTo(4711); softly.assertThat(underTest.getUsername()) - .as(HttpProxyConfigValue.USER_NAME.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) .isEqualTo("john.frume"); softly.assertThat(underTest.getPassword()) - .as(HttpProxyConfigValue.PASSWORD.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) .isEqualTo("verySecretPW!"); } diff --git a/internal/utils/http/src/test/resources/http-proxy-test.conf b/internal/utils/http/src/test/resources/http-proxy-test.conf new file mode 100644 index 0000000000..2d51e8eeb7 --- /dev/null +++ b/internal/utils/http/src/test/resources/http-proxy-test.conf @@ -0,0 +1,9 @@ +http { + proxy { + enabled = true + hostname = "example.com" + port = 4711 + username = "john.frume" + password = "verySecretPW!" + } +} \ No newline at end of file diff --git a/internal/utils/json/pom.xml b/internal/utils/json/pom.xml new file mode 100755 index 0000000000..69c9b9a106 --- /dev/null +++ b/internal/utils/json/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + + org.eclipse.ditto + ditto-internal-utils + ${revision} + + + ditto-internal-utils-json + Eclipse Ditto :: Internal :: Utils :: JSON + + + + org.eclipse.ditto + ditto-json + + + org.eclipse.ditto + ditto-json-cbor + + + + diff --git a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoader.java b/internal/utils/json/src/main/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoader.java similarity index 90% rename from internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoader.java rename to internal/utils/json/src/main/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoader.java index e11e3ba6eb..44dee1c64e 100644 --- a/internal/utils/cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoader.java +++ b/internal/utils/json/src/main/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.internal.utils.cluster; +package org.eclipse.ditto.internal.utils.json; import java.text.MessageFormat; import java.util.ServiceLoader; @@ -25,7 +25,7 @@ * is thrown. */ @ThreadSafe -final class CborFactoryLoader { +public final class CborFactoryLoader { @Nullable private static CborFactoryLoader instance = null; @@ -38,7 +38,7 @@ private CborFactoryLoader() { super(); } - static CborFactoryLoader getInstance() { + public static CborFactoryLoader getInstance() { var result = instance; if (null == result) { result = new CborFactoryLoader(); @@ -47,7 +47,7 @@ static CborFactoryLoader getInstance() { return result; } - CborFactory getCborFactoryOrThrow() { + public CborFactory getCborFactoryOrThrow() { var result = cborFactory; // Double-Check-Idiom diff --git a/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoaderTest.java b/internal/utils/json/src/test/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoaderTest.java similarity index 91% rename from internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoaderTest.java rename to internal/utils/json/src/test/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoaderTest.java index 782757364e..d9b6c52830 100644 --- a/internal/utils/cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster/CborFactoryLoaderTest.java +++ b/internal/utils/json/src/test/java/org/eclipse/ditto/internal/utils/json/CborFactoryLoaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.internal.utils.cluster; +package org.eclipse.ditto.internal.utils.json; import static org.assertj.core.api.Assertions.assertThat; diff --git a/internal/utils/metrics-service/pom.xml b/internal/utils/metrics-service/pom.xml new file mode 100644 index 0000000000..7caf2fd268 --- /dev/null +++ b/internal/utils/metrics-service/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + + ditto-internal-utils + org.eclipse.ditto + ${revision} + + + ditto-internal-utils-metrics-service + Eclipse Ditto :: Internal :: Utils :: Metrics Service + + + + org.eclipse.ditto + ditto-internal-utils-metrics + + + io.kamon + kamon-prometheus_${scala.version} + + + io.kamon + kamon-executors_${scala.version} + + + org.apache.pekko + pekko-actor_${scala.version} + + + org.apache.pekko + pekko-http_${scala.version} + provided + + + + + com.github.scullxbones + pekko-persistence-mongodb_${scala.version} + provided + + + + nl.grons + metrics4-scala_${scala.version} + + + + diff --git a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedForkJoinExecutorServiceConfigurator.java b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedForkJoinExecutorServiceConfigurator.java similarity index 89% rename from internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedForkJoinExecutorServiceConfigurator.java rename to internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedForkJoinExecutorServiceConfigurator.java index c6df458e43..32d90ef7ab 100644 --- a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedForkJoinExecutorServiceConfigurator.java +++ b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedForkJoinExecutorServiceConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,16 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.internal.utils.metrics.executor; +package org.eclipse.ditto.internal.utils.metrics.service.executor; import java.util.concurrent.ThreadFactory; -import com.typesafe.config.Config; - import org.apache.pekko.dispatch.DispatcherPrerequisites; import org.apache.pekko.dispatch.ExecutorServiceConfigurator; import org.apache.pekko.dispatch.ExecutorServiceFactory; import org.apache.pekko.dispatch.ForkJoinExecutorConfigurator; + +import com.typesafe.config.Config; + import kamon.instrumentation.executor.ExecutorInstrumentation; /** @@ -37,7 +38,7 @@ * with *
  *   type = Dispatcher
- *   executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator"
+ *   executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
  *   fork-join-executor {
  *     ...
  *   }
diff --git a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java
similarity index 89%
rename from internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java
rename to internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java
index 47467e4b08..a7ae657f63 100644
--- a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java
+++ b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,16 +10,17 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.internal.utils.metrics.executor;
+package org.eclipse.ditto.internal.utils.metrics.service.executor;
 
 import java.util.concurrent.ThreadFactory;
 
-import com.typesafe.config.Config;
-
 import org.apache.pekko.dispatch.DispatcherPrerequisites;
 import org.apache.pekko.dispatch.ExecutorServiceConfigurator;
 import org.apache.pekko.dispatch.ExecutorServiceFactory;
 import org.apache.pekko.dispatch.ThreadPoolExecutorConfigurator;
+
+import com.typesafe.config.Config;
+
 import kamon.instrumentation.executor.ExecutorInstrumentation;
 
 /**
@@ -37,7 +38,7 @@
  * with
  * 
  *   type = Dispatcher
- *   executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+ *   executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
  *   thread-pool-executor {
  *     ...
  *   }
diff --git a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/mongo/MongoMetricsBuilder.java b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/mongo/MongoMetricsBuilder.java
similarity index 94%
rename from internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/mongo/MongoMetricsBuilder.java
rename to internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/mongo/MongoMetricsBuilder.java
index 1b236b3ae0..c738e028bd 100644
--- a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/mongo/MongoMetricsBuilder.java
+++ b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/mongo/MongoMetricsBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.internal.utils.metrics.mongo;
+package org.eclipse.ditto.internal.utils.metrics.service.mongo;
 
 import java.util.concurrent.atomic.LongAccumulator;
 
diff --git a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/prometheus/PrometheusReporterRoute.java b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/prometheus/PrometheusReporterRoute.java
similarity index 93%
rename from internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/prometheus/PrometheusReporterRoute.java
rename to internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/prometheus/PrometheusReporterRoute.java
index bb8163089c..39a89b4874 100644
--- a/internal/utils/metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics/prometheus/PrometheusReporterRoute.java
+++ b/internal/utils/metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service/prometheus/PrometheusReporterRoute.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.internal.utils.metrics.prometheus;
+package org.eclipse.ditto.internal.utils.metrics.service.prometheus;
 
 import static org.apache.pekko.http.javadsl.server.Directives.complete;
 import static org.apache.pekko.http.javadsl.server.Directives.get;
@@ -21,6 +21,7 @@
 import org.apache.pekko.http.javadsl.model.StatusCodes;
 import org.apache.pekko.http.javadsl.server.Route;
 import org.apache.pekko.util.ByteString;
+
 import kamon.prometheus.PrometheusReporter;
 
 /**
diff --git a/internal/utils/metrics/pom.xml b/internal/utils/metrics/pom.xml
index 8086c514ad..f6a47b225c 100644
--- a/internal/utils/metrics/pom.xml
+++ b/internal/utils/metrics/pom.xml
@@ -14,12 +14,13 @@
 
+    4.0.0
+
     
         ditto-internal-utils
         org.eclipse.ditto
         ${revision}
     
-    4.0.0
 
     ditto-internal-utils-metrics
     Eclipse Ditto :: Internal :: Utils :: Metrics
@@ -37,37 +38,6 @@
             io.kamon
             kamon-core_${scala.version}
         
-        
-            io.kamon
-            kamon-prometheus_${scala.version}
-        
-        
-            io.kamon
-            kamon-executors_${scala.version}
-        
-        
-            org.apache.pekko
-            pekko-actor_${scala.version}
-        
-        
-            org.apache.pekko
-            pekko-http_${scala.version}
-            provided
-        
-
-        
-            
-            com.github.scullxbones
-            pekko-persistence-mongodb_${scala.version}
-            provided
-        
-        
-            
-            nl.grons
-            metrics4-scala_${scala.version}
-        
     
 
 
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/config/DefaultMetricsConfigTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/config/DefaultMetricsConfigTest.java
index d8410f9277..eca88e7ad0 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/config/DefaultMetricsConfigTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/config/DefaultMetricsConfigTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/counter/KamonCounterTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/counter/KamonCounterTest.java
index 8b41794d20..ee78c7a14a 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/counter/KamonCounterTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/counter/KamonCounterTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/gauge/KamonGaugeTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/gauge/KamonGaugeTest.java
index cd20c786b8..f7c11a6a12 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/gauge/KamonGaugeTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/gauge/KamonGaugeTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/histogram/KamonHistogramTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/histogram/KamonHistogramTest.java
index 1d6df9a49a..f443360883 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/histogram/KamonHistogramTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/histogram/KamonHistogramTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/KamonTagSetConverterTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/KamonTagSetConverterTest.java
index 848e5fc8e6..e3092ec492 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/KamonTagSetConverterTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/KamonTagSetConverterTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagSetTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagSetTest.java
index ec7d91724e..f947c6fac0 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagSetTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagSetTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagTest.java
index 09b85a03be..172ca79dcc 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/tag/TagTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/PreparedKamonTimerTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/PreparedKamonTimerTest.java
index 70f19ae919..3908bd6e1a 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/PreparedKamonTimerTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/PreparedKamonTimerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartInstantTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartInstantTest.java
index fbb4f937e4..6593545077 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartInstantTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartInstantTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartedKamonTimerTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartedKamonTimerTest.java
index f926d74848..e060d36861 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartedKamonTimerTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StartedKamonTimerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StoppedKamonTimerTest.java b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StoppedKamonTimerTest.java
index 6e62efc044..0afbee33b8 100644
--- a/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StoppedKamonTimerTest.java
+++ b/internal/utils/metrics/src/test/java/org/eclipse/ditto/internal/utils/metrics/instruments/timer/StoppedKamonTimerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/cleanup/Credits.java b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/cleanup/Credits.java
index 6ad4112bc9..f8e6ce4866 100644
--- a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/cleanup/Credits.java
+++ b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/cleanup/Credits.java
@@ -15,14 +15,12 @@
 import java.time.Duration;
 import java.util.concurrent.atomic.LongAccumulator;
 
-import org.eclipse.ditto.internal.utils.pekko.controlflow.Transistor;
-import org.eclipse.ditto.internal.utils.metrics.mongo.MongoMetricsBuilder;
-
 import org.apache.pekko.NotUsed;
 import org.apache.pekko.event.LoggingAdapter;
 import org.apache.pekko.stream.SourceShape;
 import org.apache.pekko.stream.javadsl.GraphDSL;
 import org.apache.pekko.stream.javadsl.Source;
+import org.eclipse.ditto.internal.utils.pekko.controlflow.Transistor;
 
 final class Credits {
 
@@ -36,7 +34,11 @@ final class Credits {
     }
 
     static Credits of(final CleanupConfig config) {
-        return new Credits(config, MongoMetricsBuilder.maxTimerNanos());
+        return new Credits(config, createMaxTimerNanos());
+    }
+
+    private static LongAccumulator createMaxTimerNanos() {
+        return new LongAccumulator(Math::max, 0L);
     }
 
     /**
diff --git a/internal/utils/pom.xml b/internal/utils/pom.xml
index 541e144da9..af8f64bd7d 100755
--- a/internal/utils/pom.xml
+++ b/internal/utils/pom.xml
@@ -35,6 +35,7 @@
         ddata
         health
         http
+        json
         jwt
         namespaces
         persistence
@@ -46,6 +47,7 @@
         test
         tracing
         metrics
+        metrics-service
         persistent-actors
         extension
     
diff --git a/internal/utils/protocol/pom.xml b/internal/utils/protocol/pom.xml
index 56ba2d96ba..536975e372 100755
--- a/internal/utils/protocol/pom.xml
+++ b/internal/utils/protocol/pom.xml
@@ -38,6 +38,11 @@
             com.typesafe
             config
         
+
+        
+            org.apache.pekko
+            pekko-actor_${scala.version}
+        
     
 
 
diff --git a/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java b/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java
index 853509dbeb..0ef662ee9b 100644
--- a/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java
+++ b/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java
@@ -24,7 +24,7 @@
 /**
  * Implementation of {@link SerializationContext} backed by Jackson's {@link JsonGenerator}.
  */
-final class JacksonSerializationContext implements SerializationContext {
+public final class JacksonSerializationContext implements SerializationContext {
 
     private final JsonGenerator jacksonGenerator;
     private final ControllableOutputStream outputStream;
diff --git a/policies/enforcement/src/main/resources/reference.conf b/policies/enforcement/src/main/resources/reference.conf
index 6cc7c16513..1b623b87cd 100644
--- a/policies/enforcement/src/main/resources/reference.conf
+++ b/policies/enforcement/src/main/resources/reference.conf
@@ -3,12 +3,12 @@
 
 enforcement-cache-dispatcher {
   type = "Dispatcher"
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
 }
 
 enforcement-dispatcher {
   type = "Dispatcher"
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
 }
 
 ditto.extensions {
diff --git a/policies/service/src/main/resources/policies.conf b/policies/service/src/main/resources/policies.conf
index da0d62dc87..43b35a2524 100755
--- a/policies/service/src/main/resources/policies.conf
+++ b/policies/service/src/main/resources/policies.conf
@@ -338,7 +338,7 @@ policy-journal-persistence-dispatcher {
   # which mailbox to use
   mailbox-type = "org.eclipse.ditto.policies.service.persistence.actors.PolicyPersistenceActorMailbox"
   mailbox-capacity = 100
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
   fork-join-executor {
     parallelism-min = 4
     parallelism-factor = 3.0
@@ -353,7 +353,7 @@ policy-snaps-persistence-dispatcher {
   # which mailbox to use
   mailbox-type = "org.eclipse.ditto.policies.service.persistence.actors.PolicyPersistenceActorMailbox"
   mailbox-capacity = 100
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
   fork-join-executor {
     parallelism-min = 4
     parallelism-factor = 3.0
@@ -365,7 +365,7 @@ policy-snaps-persistence-dispatcher {
 
 blocked-namespaces-dispatcher {
   type = Dispatcher
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
   fork-join-executor {
     # Min number of threads to cap factor-based parallelism number to
     parallelism-min = 4
diff --git a/things/service/pom.xml b/things/service/pom.xml
index 49013ab40c..0383a0c007 100644
--- a/things/service/pom.xml
+++ b/things/service/pom.xml
@@ -83,6 +83,10 @@
             org.eclipse.ditto
             ditto-wot-model
         
+        
+            org.eclipse.ditto
+            ditto-wot-validation
+        
         
             org.eclipse.ditto
             ditto-wot-integration
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java
index 7570d5bcc6..611ea6ee93 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java
@@ -30,8 +30,8 @@
 import org.eclipse.ditto.internal.utils.persistence.operations.DefaultPersistenceOperationsConfig;
 import org.eclipse.ditto.internal.utils.persistence.operations.PersistenceOperationsConfig;
 import org.eclipse.ditto.internal.utils.tracing.config.TracingConfig;
-import org.eclipse.ditto.wot.integration.config.DefaultWotConfig;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
+import org.eclipse.ditto.wot.api.config.DefaultWotConfig;
+import org.eclipse.ditto.wot.api.config.WotConfig;
 
 /**
  * This class implements the config of the Ditto Things service.
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java
index 0e26ac4b46..327e942eeb 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java
@@ -19,7 +19,7 @@
 import org.eclipse.ditto.internal.utils.health.config.WithHealthCheckConfig;
 import org.eclipse.ditto.internal.utils.persistence.mongo.config.WithMongoDbConfig;
 import org.eclipse.ditto.internal.utils.persistence.operations.WithPersistenceOperationsConfig;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
+import org.eclipse.ditto.wot.api.config.WotConfig;
 
 /**
  * Provides the configuration settings of the Things service.
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java
index ea04c8b4f0..67360f2d98 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java
@@ -21,6 +21,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.entity.metadata.MetadataBuilder;
 import org.eclipse.ditto.base.model.entity.metadata.MetadataModelFactory;
@@ -45,6 +46,10 @@
 import org.eclipse.ditto.things.model.signals.commands.modify.ThingModifyCommand;
 import org.eclipse.ditto.things.model.signals.commands.query.ThingQueryCommand;
 import org.eclipse.ditto.things.model.signals.events.ThingEvent;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
+import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator;
+import org.eclipse.ditto.wot.api.validator.WotThingModelValidator;
+import org.eclipse.ditto.wot.integration.DittoWotIntegration;
 
 /**
  * Abstract base class for {@link org.eclipse.ditto.things.model.signals.commands.ThingCommand} strategies.
@@ -59,8 +64,16 @@ abstract class AbstractThingCommandStrategy>
     private static final ConditionalHeadersValidator VALIDATOR =
             ThingsConditionalHeadersValidatorProvider.getInstance();
 
-    protected AbstractThingCommandStrategy(final Class theMatchingClass) {
+    protected final WotThingDescriptionGenerator wotThingDescriptionGenerator;
+    protected final WotThingSkeletonGenerator wotThingSkeletonGenerator;
+    protected final WotThingModelValidator wotThingModelValidator;
+
+    protected AbstractThingCommandStrategy(final Class theMatchingClass, final ActorSystem actorSystem) {
         super(theMatchingClass);
+        final DittoWotIntegration wotIntegration = DittoWotIntegration.get(actorSystem);
+        wotThingDescriptionGenerator = wotIntegration.getWotThingDescriptionGenerator();
+        wotThingSkeletonGenerator = wotIntegration.getWotThingSkeletonGenerator();
+        wotThingModelValidator = wotIntegration.getWotThingModelValidator();
     }
 
     @Override
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java
index 7f34c850ca..a9ad37378f 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java
@@ -23,6 +23,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
@@ -42,9 +43,6 @@
 import org.eclipse.ditto.things.model.signals.commands.modify.CreateThingResponse;
 import org.eclipse.ditto.things.model.signals.events.ThingCreated;
 import org.eclipse.ditto.things.model.signals.events.ThingEvent;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
-
-import org.apache.pekko.actor.ActorSystem;
 
 /**
  * This strategy handles the {@link CreateThingStrategy} command.
@@ -52,16 +50,13 @@
 @Immutable
 final class CreateThingStrategy extends AbstractThingCommandStrategy {
 
-    private final WotThingDescriptionProvider wotThingDescriptionProvider;
-
     /**
      * Constructs a new {@link CreateThingStrategy} object.
      *
      * @param actorSystem the actor system to use for loading the WoT extension.
      */
     CreateThingStrategy(final ActorSystem actorSystem) {
-        super(CreateThing.class);
-        wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem);
+        super(CreateThing.class, actorSystem);
     }
 
     @Override
@@ -110,7 +105,7 @@ protected Result> doApply(final Context context,
         final Instant now = Instant.now();
 
         final Thing finalNewThing = newThing;
-        final CompletionStage thingStage = wotThingDescriptionProvider.provideThingSkeletonForCreation(
+        final CompletionStage thingStage = wotThingSkeletonGenerator.provideThingSkeletonForCreation(
                         command.getEntityId(),
                         newThing.getDefinition().orElse(null),
                         commandHeaders
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java
index 8a1f6d9891..f7f11bc125 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java
@@ -17,16 +17,17 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import org.eclipse.ditto.json.JsonPointer;
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
+import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
+import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory;
+import org.eclipse.ditto.json.JsonPointer;
 import org.eclipse.ditto.things.model.Attributes;
 import org.eclipse.ditto.things.model.Thing;
 import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
-import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory;
 import org.eclipse.ditto.things.model.signals.commands.modify.DeleteAttribute;
 import org.eclipse.ditto.things.model.signals.commands.modify.DeleteAttributeResponse;
 import org.eclipse.ditto.things.model.signals.events.AttributeDeleted;
@@ -40,9 +41,11 @@ final class DeleteAttributeStrategy extends AbstractThingCommandStrategy
 
     /**
      * Constructs a new {@code MergeThingStrategy} object.
+     *
+     * @param actorSystem the actor system to use for loading the WoT extension.
      */
-    MergeThingStrategy() {
-        super(MergeThing.class);
+    MergeThingStrategy(final ActorSystem actorSystem) {
+        super(MergeThing.class, actorSystem);
     }
 
     @Override
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java
index 4baca49012..dc38fba537 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java
@@ -17,17 +17,18 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import org.eclipse.ditto.json.JsonObject;
-import org.eclipse.ditto.json.JsonPointer;
-import org.eclipse.ditto.json.JsonValue;
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
-import org.eclipse.ditto.things.model.Thing;
-import org.eclipse.ditto.things.model.ThingId;
 import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
 import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.json.JsonPointer;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.things.model.ThingId;
 import org.eclipse.ditto.things.model.signals.commands.ThingCommandSizeValidator;
 import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttribute;
 import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttributeResponse;
@@ -43,9 +44,11 @@ final class ModifyAttributeStrategy extends AbstractThingCommandStrategy {
 
-    private final WotThingDescriptionProvider wotThingDescriptionProvider;
-
     /**
      * Constructs a new {@code ModifyFeatureStrategy} object.
      *
      * @param actorSystem the actor system to use for loading the WoT extension.
      */
     ModifyFeatureStrategy(final ActorSystem actorSystem) {
-        super(ModifyFeature.class);
-        wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem);
+        super(ModifyFeature.class, actorSystem);
     }
 
     @Override
@@ -93,7 +90,17 @@ protected Result> doApply(final Context context,
                 command::getDittoHeaders);
 
         return extractFeature(command, nonNullThing)
-                .map(feature -> getModifyResult(context, nextRevision, command, thing, metadata))
+                .map(feature -> {
+                    final Optional featureDefinition = feature.getDefinition();
+                    // validate based on potentially referenced Feature WoT TM
+                    final CompletionStage validatedStage;
+                    if (featureDefinition.isPresent()) {
+                        validatedStage = wotThingModelValidator.validateFeature(feature, command.getDittoHeaders());
+                    } else {
+                        validatedStage = CompletableFuture.completedStage(null);
+                    }
+                    return getModifyResult(context, nextRevision, command, thing, metadata, validatedStage);
+                })
                 .orElseGet(() -> getCreateResult(context, nextRevision, command, thing, metadata));
     }
 
@@ -103,18 +110,24 @@ private Optional extractFeature(final ModifyFeature command, @Nullable
     }
 
     private Result> getModifyResult(final Context context, final long nextRevision,
-            final ModifyFeature command, @Nullable final Thing thing, @Nullable final Metadata metadata) {
+            final ModifyFeature command, @Nullable final Thing thing, @Nullable final Metadata metadata,
+            final CompletionStage validatedStage) {
 
         final DittoHeaders dittoHeaders = command.getDittoHeaders();
 
-        final ThingEvent event =
-                FeatureModified.of(command.getEntityId(), command.getFeature(), nextRevision, getEventTimestamp(),
-                        dittoHeaders, metadata);
-        final WithDittoHeaders response = appendETagHeaderIfProvided(command,
-                ModifyFeatureResponse.modified(context.getState(), command.getFeatureId(), dittoHeaders),
-                thing);
-
-        return ResultFactory.newMutationResult(command, event, response);
+        final CompletionStage> eventStage =
+                validatedStage.thenApply(aVoid ->
+                        FeatureModified.of(command.getEntityId(), command.getFeature(), nextRevision,
+                                getEventTimestamp(),
+                                dittoHeaders, metadata));
+        final CompletionStage responseStage =
+                validatedStage.thenApply(aVoid ->
+                        appendETagHeaderIfProvided(command,
+                                ModifyFeatureResponse.modified(context.getState(), command.getFeatureId(),
+                                        dittoHeaders),
+                                thing));
+
+        return ResultFactory.newMutationResult(command, eventStage, responseStage);
     }
 
     private Result> getCreateResult(final Context context, final long nextRevision,
@@ -123,7 +136,7 @@ private Result> getCreateResult(final Context context, fi
         final DittoHeaders dittoHeaders = command.getDittoHeaders();
 
         final Feature finalNewFeature = command.getFeature();
-        final CompletionStage featureStage = wotThingDescriptionProvider.provideFeatureSkeletonForCreation(
+        final CompletionStage featureStage = wotThingSkeletonGenerator.provideFeatureSkeletonForCreation(
                         finalNewFeature.getId(),
                         finalNewFeature.getDefinition().orElse(null),
                         dittoHeaders
@@ -155,15 +168,21 @@ private Result> getCreateResult(final Context context, fi
                         .orElse(finalNewFeature)
                 );
 
+        final Function> validationFunction = feature ->
+                wotThingModelValidator.validateFeature(feature, command.getDittoHeaders())
+                        .thenApply(aVoid -> feature);
         final CompletionStage> eventStage =
-                featureStage.thenApply(feature -> FeatureCreated.of(command.getEntityId(), feature, nextRevision,
-                        getEventTimestamp(), dittoHeaders,
-                        metadata));
-
-        final CompletionStage response = featureStage.thenApply(modFeature ->
-                appendETagHeaderIfProvided(command,
-                        ModifyFeatureResponse.created(context.getState(), modFeature, dittoHeaders), thing)
-        );
+                featureStage.thenCompose(validationFunction).thenApply(feature ->
+                        FeatureCreated.of(command.getEntityId(), feature, nextRevision,
+                                getEventTimestamp(), dittoHeaders,
+                                metadata)
+                );
+
+        final CompletionStage response = featureStage.thenCompose(validationFunction)
+                .thenApply(modFeature ->
+                        appendETagHeaderIfProvided(command,
+                                ModifyFeatureResponse.created(context.getState(), modFeature, dittoHeaders), thing)
+                );
 
         return ResultFactory.newMutationResult(command, eventStage, response);
     }
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java
index eb60ba4309..d08ac878b5 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java
@@ -25,6 +25,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
@@ -47,9 +48,6 @@
 import org.eclipse.ditto.things.model.signals.events.FeaturesCreated;
 import org.eclipse.ditto.things.model.signals.events.FeaturesModified;
 import org.eclipse.ditto.things.model.signals.events.ThingEvent;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
-
-import org.apache.pekko.actor.ActorSystem;
 
 /**
  * This strategy handles the {@link org.eclipse.ditto.things.model.signals.commands.modify.ModifyFeatures} command.
@@ -57,16 +55,13 @@
 @Immutable
 final class ModifyFeaturesStrategy extends AbstractThingCommandStrategy {
 
-    private final WotThingDescriptionProvider wotThingDescriptionProvider;
-
     /**
      * Constructs a new {@code ModifyFeaturesStrategy} object.
      *
      * @param actorSystem the actor system to use for loading the WoT extension.
      */
     ModifyFeaturesStrategy(final ActorSystem actorSystem) {
-        super(ModifyFeatures.class);
-        wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem);
+        super(ModifyFeatures.class, actorSystem);
     }
 
     @Override
@@ -120,7 +115,7 @@ private Result> getCreateResult(final Context context, fi
 
         final List> featureStages = command.getFeatures()
                 .stream()
-                .map(feature -> wotThingDescriptionProvider.provideFeatureSkeletonForCreation(
+                .map(feature -> wotThingSkeletonGenerator.provideFeatureSkeletonForCreation(
                                         feature.getId(),
                                         feature.getDefinition().orElse(null),
                                         dittoHeaders
@@ -153,7 +148,7 @@ private Result> getCreateResult(final Context context, fi
                 .toList();
 
         final CompletableFuture featuresStage =
-                CompletableFuture.allOf(featureStages.toArray(new CompletableFuture[0]))
+                CompletableFuture.allOf(featureStages.toArray(CompletableFuture[]::new))
                         .thenApply(aVoid ->
                                 ThingsModelFactory.newFeatures(
                                         featureStages.stream()
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java
index 619214bf16..7bdc0cbba4 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java
@@ -17,6 +17,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
@@ -39,9 +40,11 @@ final class ModifyPolicyIdStrategy extends AbstractThingCommandStrategy> applyModifyCommand(final Context context,
         final DittoHeaders dittoHeaders = command.getDittoHeaders();
 
         final Thing modifiedThing = applyThingModifications(command.getThing(), thing, eventTs, nextRevision);
-
-        final ThingEvent event =
-                ThingModified.of(modifiedThing, nextRevision, eventTs, dittoHeaders, metadata);
-        final WithDittoHeaders response = appendETagHeaderIfProvided(command,
-                ModifyThingResponse.modified(context.getState(), dittoHeaders), modifiedThing);
-
-        return ResultFactory.newMutationResult(command, event, response);
+        // validate based on potentially referenced Thing WoT TM/TD
+        final CompletionStage validatedStage = wotThingModelValidator
+                .validateThing(modifiedThing, command.getDittoHeaders());
+
+        final CompletionStage> eventStage =
+                validatedStage.thenApply(aVoid ->
+                        ThingModified.of(modifiedThing, nextRevision, eventTs, dittoHeaders, metadata));
+        final CompletionStage responseStage =
+                validatedStage.thenApply(aVoid ->
+                        appendETagHeaderIfProvided(command,
+                                ModifyThingResponse.modified(context.getState(), dittoHeaders), modifiedThing));
+
+        return ResultFactory.newMutationResult(command, eventStage, responseStage);
     }
 
     /**
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java
index 9a689a58a9..077099779d 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java
@@ -17,16 +17,17 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import org.eclipse.ditto.json.JsonObject;
-import org.eclipse.ditto.json.JsonPointer;
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
+import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
+import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.json.JsonPointer;
 import org.eclipse.ditto.things.model.Attributes;
 import org.eclipse.ditto.things.model.Thing;
 import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
-import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory;
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveAttribute;
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveAttributeResponse;
 import org.eclipse.ditto.things.model.signals.events.ThingEvent;
@@ -39,9 +40,11 @@ final class RetrieveAttributeStrategy extends AbstractThingCommandStrategy {
 
-    private final WotThingDescriptionProvider wotThingDescriptionProvider;
-
     /**
      * Constructs a new {@code RetrieveFeatureStrategy} object.
      *
      * @param actorSystem the actor system to use for loading the WoT extension.
      */
     RetrieveFeatureStrategy(final ActorSystem actorSystem) {
-        super(RetrieveFeature.class);
-        wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem);
+        super(RetrieveFeature.class, actorSystem);
     }
 
     @Override
@@ -96,7 +92,7 @@ private CompletionStage getRetrieveThingDescriptionResponse(@N
         if (thing != null) {
             return thing.getFeatures()
                     .flatMap(f -> f.getFeature(featureId))
-                    .map(feature -> wotThingDescriptionProvider
+                    .map(feature -> wotThingDescriptionGenerator
                             .provideFeatureTD(command.getEntityId(), thing, feature, command.getDittoHeaders())
                     )
                     .map(tdStage -> tdStage.thenApply(td ->
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java
index b2b38c3d42..ecc943efe3 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java
@@ -18,6 +18,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
@@ -43,9 +44,11 @@ final class RetrieveFeaturesStrategy extends AbstractThingCommandStrategy {
 
-    private final WotThingDescriptionProvider wotThingDescriptionProvider;
-
     /**
      * Constructs a new {@code RetrieveThingStrategy} object.
      *
      * @param actorSystem the actor system to use for loading the WoT extension.
      */
     RetrieveThingStrategy(final ActorSystem actorSystem) {
-        super(RetrieveThing.class);
-        wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem);
+        super(RetrieveThing.class, actorSystem);
     }
 
     @Override
@@ -126,7 +122,7 @@ private static JsonObject getThingJson(final Thing thing, final ThingQueryComman
     private CompletionStage getRetrieveThingDescriptionResponse(@Nullable final Thing thing,
             final RetrieveThing command) {
         if (thing != null) {
-            return wotThingDescriptionProvider
+            return wotThingDescriptionGenerator
                     .provideThingTD(thing.getDefinition().orElse(null),
                             command.getEntityId(),
                             thing,
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java
index 0bbaa07d82..b29fcb3671 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java
@@ -18,6 +18,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
 import org.eclipse.ditto.base.model.json.FieldType;
@@ -43,9 +44,11 @@ final class SudoRetrieveThingStrategy extends AbstractThingCommandStrategy> getCre
     }
 
     private void addThingStrategies(final ActorSystem system) {
-        addStrategy(new ThingConflictStrategy());
-        addStrategy(new ModifyThingStrategy());
+        addStrategy(new ThingConflictStrategy(system));
+        addStrategy(new ModifyThingStrategy(system));
         addStrategy(new RetrieveThingStrategy(system));
-        addStrategy(new DeleteThingStrategy());
-        addStrategy(new MergeThingStrategy());
+        addStrategy(new DeleteThingStrategy(system));
+        addStrategy(new MergeThingStrategy(system));
     }
 
-    private void addPolicyStrategies() {
-        addStrategy(new RetrievePolicyIdStrategy());
-        addStrategy(new ModifyPolicyIdStrategy());
+    private void addPolicyStrategies(final ActorSystem system) {
+        addStrategy(new RetrievePolicyIdStrategy(system));
+        addStrategy(new ModifyPolicyIdStrategy(system));
     }
 
-    private void addAttributesStrategies() {
-        addStrategy(new ModifyAttributesStrategy());
-        addStrategy(new ModifyAttributeStrategy());
-        addStrategy(new RetrieveAttributesStrategy());
-        addStrategy(new RetrieveAttributeStrategy());
-        addStrategy(new DeleteAttributesStrategy());
-        addStrategy(new DeleteAttributeStrategy());
+    private void addAttributesStrategies(final ActorSystem system) {
+        addStrategy(new ModifyAttributesStrategy(system));
+        addStrategy(new ModifyAttributeStrategy(system));
+        addStrategy(new RetrieveAttributesStrategy(system));
+        addStrategy(new RetrieveAttributeStrategy(system));
+        addStrategy(new DeleteAttributesStrategy(system));
+        addStrategy(new DeleteAttributeStrategy(system));
     }
 
-    private void addDefinitionStrategies() {
-        addStrategy(new ModifyThingDefinitionStrategy());
-        addStrategy(new RetrieveThingDefinitionStrategy());
-        addStrategy(new DeleteThingDefinitionStrategy());
+    private void addDefinitionStrategies(final ActorSystem system) {
+        addStrategy(new ModifyThingDefinitionStrategy(system));
+        addStrategy(new RetrieveThingDefinitionStrategy(system));
+        addStrategy(new DeleteThingDefinitionStrategy(system));
     }
 
     private void addFeaturesStrategies(final ActorSystem system) {
         addStrategy(new ModifyFeaturesStrategy(system));
         addStrategy(new ModifyFeatureStrategy(system));
-        addStrategy(new RetrieveFeaturesStrategy());
+        addStrategy(new RetrieveFeaturesStrategy(system));
         addStrategy(new RetrieveFeatureStrategy(system));
-        addStrategy(new DeleteFeaturesStrategy());
-        addStrategy(new DeleteFeatureStrategy());
+        addStrategy(new DeleteFeaturesStrategy(system));
+        addStrategy(new DeleteFeatureStrategy(system));
     }
 
-    private void addFeatureDefinitionStrategies() {
-        addStrategy(new ModifyFeatureDefinitionStrategy());
-        addStrategy(new RetrieveFeatureDefinitionStrategy());
-        addStrategy(new DeleteFeatureDefinitionStrategy());
+    private void addFeatureDefinitionStrategies(final ActorSystem system) {
+        addStrategy(new ModifyFeatureDefinitionStrategy(system));
+        addStrategy(new RetrieveFeatureDefinitionStrategy(system));
+        addStrategy(new DeleteFeatureDefinitionStrategy(system));
     }
 
-    private void addFeaturePropertiesStrategies() {
-        addStrategy(new ModifyFeaturePropertiesStrategy());
-        addStrategy(new ModifyFeaturePropertyStrategy());
-        addStrategy(new RetrieveFeaturePropertiesStrategy());
-        addStrategy(new RetrieveFeaturePropertyStrategy());
-        addStrategy(new DeleteFeaturePropertiesStrategy());
-        addStrategy(new DeleteFeaturePropertyStrategy());
+    private void addFeaturePropertiesStrategies(final ActorSystem system) {
+        addStrategy(new ModifyFeaturePropertiesStrategy(system));
+        addStrategy(new ModifyFeaturePropertyStrategy(system));
+        addStrategy(new RetrieveFeaturePropertiesStrategy(system));
+        addStrategy(new RetrieveFeaturePropertyStrategy(system));
+        addStrategy(new DeleteFeaturePropertiesStrategy(system));
+        addStrategy(new DeleteFeaturePropertyStrategy(system));
     }
 
-    private void addFeatureDesiredPropertiesStrategies() {
-        addStrategy(new ModifyFeatureDesiredPropertiesStrategy());
-        addStrategy(new ModifyFeatureDesiredPropertyStrategy());
-        addStrategy(new RetrieveFeatureDesiredPropertiesStrategy());
-        addStrategy(new RetrieveFeatureDesiredPropertyStrategy());
-        addStrategy(new DeleteFeatureDesiredPropertiesStrategy());
-        addStrategy(new DeleteFeatureDesiredPropertyStrategy());
+    private void addFeatureDesiredPropertiesStrategies(final ActorSystem system) {
+        addStrategy(new ModifyFeatureDesiredPropertiesStrategy(system));
+        addStrategy(new ModifyFeatureDesiredPropertyStrategy(system));
+        addStrategy(new RetrieveFeatureDesiredPropertiesStrategy(system));
+        addStrategy(new RetrieveFeatureDesiredPropertyStrategy(system));
+        addStrategy(new DeleteFeatureDesiredPropertiesStrategy(system));
+        addStrategy(new DeleteFeatureDesiredPropertyStrategy(system));
     }
 
-    private void addSudoStrategies() {
-        addStrategy(new SudoRetrieveThingStrategy());
+    private void addSudoStrategies(final ActorSystem system) {
+        addStrategy(new SudoRetrieveThingStrategy(system));
     }
 
     @Override
diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java
index 2b1d2e40e6..1baee61899 100644
--- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java
+++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java
@@ -18,6 +18,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
 import org.eclipse.ditto.internal.utils.persistentactors.results.Result;
@@ -36,9 +37,11 @@ final class ThingConflictStrategy extends AbstractThingCommandStrategy, T extends ThingModifiedEvent> T asser
         return assertModificationResult(result, expectedEventClass, expectedCommandResponse, becomeDeleted);
     }
 
+    protected static , T extends ThingModifiedEvent> T assertStagedModificationResult(
+            final CommandStrategy> underTest,
+            @Nullable final Thing thing,
+            final C command,
+            final Class expectedEventClass,
+            final CommandResponse expectedCommandResponse) {
+
+        final CommandStrategy.Context context = getDefaultContext();
+        final Result> result = applyStrategy(underTest, context, thing, command);
+
+        return assertStagedModificationResult(result, expectedEventClass, expectedCommandResponse, false);
+    }
+
     protected static , T extends ThingModifiedEvent> T assertStagedModificationResult(
             final CommandStrategy> underTest,
             @Nullable final Thing thing,
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategyTest.java
index c72feb488f..7cf677a8ad 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -31,6 +32,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteAttributeStrategy}.
  */
@@ -40,7 +43,8 @@ public final class DeleteAttributeStrategyTest extends AbstractCommandStrategyTe
 
     @Before
     public void setUp() {
-        underTest = new DeleteAttributeStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteAttributeStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributesStrategyTest.java
index 330c34064a..2869340b4b 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributesStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -26,6 +27,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteAttributesStrategy}.
  */
@@ -35,7 +38,8 @@ public final class DeleteAttributesStrategyTest extends AbstractCommandStrategyT
 
     @Before
     public void setUp() {
-        underTest = new DeleteAttributesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteAttributesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDefinitionStrategyTest.java
index 7f0f2ec2f6..0fe2711784 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDefinitionStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeatureDefinitionStrategy}.
  */
@@ -38,7 +41,8 @@ public final class DeleteFeatureDefinitionStrategyTest extends AbstractCommandSt
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeatureDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeatureDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertiesStrategyTest.java
index e3d37d51e2..7a2b350c8b 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertiesStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeatureDesiredPropertiesStrategy}.
  */
@@ -38,7 +41,8 @@ public final class DeleteFeatureDesiredPropertiesStrategyTest extends AbstractCo
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeatureDesiredPropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeatureDesiredPropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertyStrategyTest.java
index 28b0b41132..110237cd2e 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureDesiredPropertyStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -32,6 +33,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeatureDesiredPropertyStrategy}.
  */
@@ -50,7 +53,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeatureDesiredPropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeatureDesiredPropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertiesStrategyTest.java
index 05558aa6d0..070c4b6954 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertiesStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeaturePropertiesStrategy}.
  */
@@ -38,7 +41,8 @@ public final class DeleteFeaturePropertiesStrategyTest extends AbstractCommandSt
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeaturePropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeaturePropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertyStrategyTest.java
index d2bf551920..edf2a46064 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturePropertyStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -32,6 +33,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeaturePropertyStrategy}.
  */
@@ -50,7 +53,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeaturePropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeaturePropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureStrategyTest.java
index 7f8057996f..0ea5a42999 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeatureStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -24,11 +25,12 @@
 import org.eclipse.ditto.things.model.signals.commands.modify.DeleteFeature;
 import org.eclipse.ditto.things.model.signals.commands.modify.DeleteFeatureResponse;
 import org.eclipse.ditto.things.model.signals.events.FeatureDeleted;
-
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeatureStrategy}.
  */
@@ -45,7 +47,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeatureStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeatureStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturesStrategyTest.java
index bf97b625be..a8f83a4068 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteFeaturesStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -26,6 +27,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteFeaturesStrategy}.
  */
@@ -35,7 +38,8 @@ public final class DeleteFeaturesStrategyTest extends AbstractCommandStrategyTes
 
     @Before
     public void setUp() {
-        underTest = new DeleteFeaturesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteFeaturesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingDefinitionStrategyTest.java
index e616ebbeaa..119c96eab7 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingDefinitionStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -26,6 +27,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteThingDefinitionStrategy}.
  */
@@ -35,7 +38,8 @@ public final class DeleteThingDefinitionStrategyTest extends AbstractCommandStra
 
     @Before
     public void setUp() {
-        underTest = new DeleteThingDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteThingDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingStrategyTest.java
index 9b615559a3..00a59859f7 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteThingStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -25,6 +26,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link DeleteThingStrategy}.
  */
@@ -34,7 +37,8 @@ public final class DeleteThingStrategyTest extends AbstractCommandStrategyTest {
 
     @Before
     public void setUp() {
-        underTest = new DeleteThingStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new DeleteThingStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/MergeThingStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/MergeThingStrategyTest.java
index d5c31beb63..d81690ac85 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/MergeThingStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/MergeThingStrategyTest.java
@@ -20,6 +20,7 @@
 
 import java.util.UUID;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.json.JsonObject;
@@ -36,6 +37,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link MergeThingStrategy}.
  */
@@ -45,7 +48,8 @@ public final class MergeThingStrategyTest extends AbstractCommandStrategyTest {
 
     @Before
     public void setUp() {
-        underTest = new MergeThingStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new MergeThingStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategyTest.java
index b997b026e8..4acb121b27 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.json.JsonFactory;
@@ -30,6 +31,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyAttributeStrategy}.
  */
@@ -48,7 +51,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyAttributeStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyAttributeStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributesStrategyTest.java
index 37327fde0f..c9fea3bbaa 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributesStrategyTest.java
@@ -16,11 +16,12 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.Attributes;
 import org.eclipse.ditto.things.model.TestConstants;
 import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttributes;
 import org.eclipse.ditto.things.model.signals.events.AttributesCreated;
 import org.eclipse.ditto.things.model.signals.events.AttributesModified;
@@ -29,6 +30,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyAttributesStrategy}.
  */
@@ -47,7 +50,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyAttributesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyAttributesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDefinitionStrategyTest.java
index a274450366..d62f1d0848 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDefinitionStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -31,6 +32,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyFeatureDefinitionStrategy}.
  */
@@ -49,7 +52,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyFeatureDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyFeatureDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertiesStrategyTest.java
index 8317665784..1d8ec48a57 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertiesStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.common.DittoSystemProperties;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
@@ -37,6 +38,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyFeatureDesiredPropertiesStrategy}.
  */
@@ -58,7 +61,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyFeatureDesiredPropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyFeatureDesiredPropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertyStrategyTest.java
index 4f7371f382..70cedc9d43 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureDesiredPropertyStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
@@ -32,11 +33,12 @@
 import org.eclipse.ditto.things.model.signals.events.FeatureDesiredPropertyCreated;
 import org.eclipse.ditto.things.model.signals.events.FeatureDesiredPropertyModified;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyFeatureDesiredPropertyStrategy}.
  */
@@ -57,7 +59,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyFeatureDesiredPropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyFeatureDesiredPropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertiesStrategyTest.java
index aa39f30fad..5e47b6023a 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertiesStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.common.DittoSystemProperties;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
@@ -37,6 +38,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyFeaturePropertiesStrategy}.
  */
@@ -58,7 +61,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyFeaturePropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyFeaturePropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertyStrategyTest.java
index 0c0447e72f..7f274941bf 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturePropertyStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.entity.metadata.Metadata;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
@@ -32,11 +33,12 @@
 import org.eclipse.ditto.things.model.signals.events.FeaturePropertyCreated;
 import org.eclipse.ditto.things.model.signals.events.FeaturePropertyModified;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyFeaturePropertyStrategy}.
  */
@@ -57,7 +59,8 @@ public static void initTestFixture() {
 
     @Before
     public void setUp() {
-        underTest = new ModifyFeaturePropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyFeaturePropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureStrategyTest.java
index cf6f35cb9f..fa1899a1e4 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeatureStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.Feature;
@@ -26,15 +27,13 @@
 import org.eclipse.ditto.things.model.signals.events.FeatureCreated;
 import org.eclipse.ditto.things.model.signals.events.FeatureModified;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.typesafe.config.ConfigFactory;
 
-import org.apache.pekko.actor.ActorSystem;
-
 /**
  * Unit test for {@link ModifyFeatureStrategy}.
  */
@@ -58,7 +57,7 @@ public void setUp() {
     @Test
     public void assertImmutability() {
         assertInstancesOf(ModifyFeatureStrategy.class, areImmutable(),
-                provided(WotThingDescriptionProvider.class).areAlsoImmutable());
+                provided(WotThingDescriptionGenerator.class).areAlsoImmutable());
     }
 
     @Test
@@ -86,7 +85,7 @@ public void modifyExistingFeature() {
         final CommandStrategy.Context context = getDefaultContext();
         final ModifyFeature command = ModifyFeature.of(context.getState(), modifiedFeature, DittoHeaders.empty());
 
-        assertModificationResult(underTest, THING_V2, command,
+        assertStagedModificationResult(underTest, THING_V2, command,
                 FeatureModified.class,
                 ETagTestUtils.modifyFeatureResponse(context.getState(), command.getFeature(), command.getDittoHeaders(), false));
     }
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategyTest.java
index 9e04f3b4b7..f07aaf0380 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.json.JsonObject;
@@ -33,15 +34,13 @@
 import org.eclipse.ditto.things.model.signals.events.FeaturesCreated;
 import org.eclipse.ditto.things.model.signals.events.FeaturesModified;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.typesafe.config.ConfigFactory;
 
-import org.apache.pekko.actor.ActorSystem;
-
 /**
  * Unit test for {@link ModifyFeaturesStrategy}.
  */
@@ -69,7 +68,7 @@ public void setUp() {
     @Test
     public void assertImmutability() {
         assertInstancesOf(ModifyFeaturesStrategy.class, areImmutable(),
-                provided(WotThingDescriptionProvider.class).areAlsoImmutable());
+                provided(WotThingDescriptionGenerator.class).areAlsoImmutable());
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategyTest.java
index 96e22b7afc..6c07417352 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -26,6 +27,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyPolicyIdStrategy}.
  */
@@ -35,7 +38,8 @@ public final class ModifyPolicyIdStrategyTest extends AbstractCommandStrategyTes
 
     @Before
     public void setUp() {
-        underTest = new ModifyPolicyIdStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyPolicyIdStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingDefinitionStrategyTest.java
index e8eb14f630..a02e1c6d1c 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingDefinitionStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -27,6 +28,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyThingDefinitionStrategy}.
  */
@@ -36,7 +39,8 @@ public final class ModifyThingDefinitionStrategyTest extends AbstractCommandStra
 
     @Before
     public void setUp() {
-        underTest = new ModifyThingDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyThingDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingStrategyTest.java
index 2f050c3d87..452d949171 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyThingStrategyTest.java
@@ -18,6 +18,7 @@
 
 import java.time.Instant;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.Thing;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ModifyThingStrategy}.
  */
@@ -39,7 +42,8 @@ public final class ModifyThingStrategyTest extends AbstractCommandStrategyTest {
 
     @Before
     public void setUp() {
-        underTest = new ModifyThingStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new ModifyThingStrategy(system);
     }
 
     @Test
@@ -64,7 +68,7 @@ public void modifyExisting() {
 
         final ModifyThing modifyThing = ModifyThing.of(thingId, thing, null, DittoHeaders.empty());
 
-        assertModificationResult(underTest, existing, modifyThing,
+        assertStagedModificationResult(underTest, existing, modifyThing,
                 ThingModified.class, ETagTestUtils.modifyThingResponse(existing, thing, modifyThing.getDittoHeaders(), false));
     }
 
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategyTest.java
index ec4013a10d..6e68cda0ae 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategyTest.java
@@ -16,6 +16,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -28,6 +29,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveAttributeStrategy}.
  */
@@ -37,7 +40,8 @@ public final class RetrieveAttributeStrategyTest extends AbstractCommandStrategy
 
     @Before
     public void setUp() {
-        underTest = new RetrieveAttributeStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveAttributeStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributesStrategyTest.java
index fdd5b3fde0..5a5a3bff02 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributesStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveAttributesStrategy}.
  */
@@ -38,7 +41,8 @@ public final class RetrieveAttributesStrategyTest extends AbstractCommandStrateg
 
     @Before
     public void setUp() {
-        underTest = new RetrieveAttributesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveAttributesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDefinitionStrategyTest.java
index 0c14629058..777c780770 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDefinitionStrategyTest.java
@@ -19,6 +19,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -29,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeatureDefinitionStrategy}.
  */
@@ -38,7 +41,8 @@ public final class RetrieveFeatureDefinitionStrategyTest extends AbstractCommand
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeatureDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeatureDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertiesStrategyTest.java
index 91decb307f..836f1f3305 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertiesStrategyTest.java
@@ -19,6 +19,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -32,6 +33,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeatureDesiredPropertiesStrategy}.
  */
@@ -41,7 +44,8 @@ public final class RetrieveFeatureDesiredPropertiesStrategyTest extends Abstract
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeatureDesiredPropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeatureDesiredPropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertyStrategyTest.java
index 02a84abc0d..cad6dc5763 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureDesiredPropertyStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -30,6 +31,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeatureDesiredPropertyStrategy}.
  */
@@ -39,7 +42,8 @@ public final class RetrieveFeatureDesiredPropertyStrategyTest extends AbstractCo
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeatureDesiredPropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeatureDesiredPropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertiesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertiesStrategyTest.java
index 71470f2fed..af4b45b0ea 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertiesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertiesStrategyTest.java
@@ -19,6 +19,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -32,6 +33,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeaturePropertiesStrategy}.
  */
@@ -41,7 +44,8 @@ public final class RetrieveFeaturePropertiesStrategyTest extends AbstractCommand
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeaturePropertiesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeaturePropertiesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertyStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertyStrategyTest.java
index 8e6f26e815..9930ce8188 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertyStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturePropertyStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -30,6 +31,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeaturePropertyStrategy}.
  */
@@ -39,7 +42,8 @@ public final class RetrieveFeaturePropertyStrategyTest extends AbstractCommandSt
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeaturePropertyStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeaturePropertyStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureStrategyTest.java
index 8ce41cb94b..7f2661bdff 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeatureStrategyTest.java
@@ -19,6 +19,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -26,14 +27,12 @@
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveFeature;
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveFeatureResponse;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.typesafe.config.ConfigFactory;
 
-import org.apache.pekko.actor.ActorSystem;
-
 /**
  * Unit test for {@link RetrieveFeatureStrategy}.
  */
@@ -50,7 +49,7 @@ public void setUp() {
     @Test
     public void assertImmutability() {
         assertInstancesOf(RetrieveFeatureStrategy.class, areImmutable(),
-                provided(WotThingDescriptionProvider.class).areAlsoImmutable());
+                provided(WotThingDescriptionGenerator.class).areAlsoImmutable());
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategyTest.java
index 4091d57d4d..968341f33f 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -34,6 +35,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveFeaturesStrategy}.
  */
@@ -43,7 +46,8 @@ public final class RetrieveFeaturesStrategyTest extends AbstractCommandStrategyT
 
     @Before
     public void setUp() {
-        underTest = new RetrieveFeaturesStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveFeaturesStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrievePolicyIdStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrievePolicyIdStrategyTest.java
index fcca0b43aa..404cc99dba 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrievePolicyIdStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrievePolicyIdStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -27,6 +28,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrievePolicyIdStrategy}.
  */
@@ -36,7 +39,8 @@ public final class RetrievePolicyIdStrategyTest extends AbstractCommandStrategyT
 
     @Before
     public void setUp() {
-        underTest = new RetrievePolicyIdStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrievePolicyIdStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingDefinitionStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingDefinitionStrategyTest.java
index 663096f71c..6b7482bbeb 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingDefinitionStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingDefinitionStrategyTest.java
@@ -17,6 +17,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
 import org.eclipse.ditto.things.model.ThingId;
@@ -27,6 +28,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link RetrieveThingDefinitionStrategy}.
  */
@@ -36,7 +39,8 @@ public final class RetrieveThingDefinitionStrategyTest extends AbstractCommandSt
 
     @Before
     public void setUp() {
-        underTest = new RetrieveThingDefinitionStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new RetrieveThingDefinitionStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingStrategyTest.java
index 81d1cf8d36..8f517fc756 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveThingStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
 import org.eclipse.ditto.internal.utils.persistentactors.commands.CommandStrategy;
@@ -32,14 +33,12 @@
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveThing;
 import org.eclipse.ditto.things.model.signals.commands.query.RetrieveThingResponse;
 import org.eclipse.ditto.things.service.persistence.actors.ETagTestUtils;
-import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.typesafe.config.ConfigFactory;
 
-import org.apache.pekko.actor.ActorSystem;
-
 /**
  * Unit test for {@link RetrieveThingStrategy}.
  */
@@ -56,7 +55,7 @@ public void setUp() {
     @Test
     public void assertImmutability() {
         assertInstancesOf(RetrieveThingStrategy.class, areImmutable(),
-                provided(WotThingDescriptionProvider.class).areAlsoImmutable());
+                provided(WotThingDescriptionGenerator.class).areAlsoImmutable());
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategyTest.java
index 6c5105f6fb..59966e17bf 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategyTest.java
@@ -18,6 +18,7 @@
 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
 import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.json.FieldType;
 import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
@@ -36,6 +37,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link SudoRetrieveThingStrategy}.
  */
@@ -45,7 +48,8 @@ public final class SudoRetrieveThingStrategyTest extends AbstractCommandStrategy
 
     @Before
     public void setUp() {
-        underTest = new SudoRetrieveThingStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        underTest = new SudoRetrieveThingStrategy(system);
     }
 
     @Test
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategyTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategyTest.java
index 454548ba81..33513afca9 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategyTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategyTest.java
@@ -20,6 +20,7 @@
 
 import java.util.concurrent.CompletionStage;
 
+import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
@@ -42,6 +43,8 @@
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import com.typesafe.config.ConfigFactory;
+
 /**
  * Unit test for {@link ThingConflictStrategy}.
  */
@@ -58,7 +61,8 @@ public void assertImmutability() {
 
     @Test
     public void createConflictResultWithoutPrecondition() {
-        final ThingConflictStrategy underTest = new ThingConflictStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        final ThingConflictStrategy underTest = new ThingConflictStrategy(system);
         final ThingId thingId = ThingId.of("thing:id");
         final Thing thing = ThingsModelFactory.newThingBuilder().setId(thingId).setRevision(25L).build();
         final CommandStrategy.Context context = DefaultContext.getInstance(thingId,
@@ -70,7 +74,8 @@ public void createConflictResultWithoutPrecondition() {
 
     @Test
     public void createPreconditionFailedResultWithPrecondition() {
-        final ThingConflictStrategy underTest = new ThingConflictStrategy();
+        final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test"));
+        final ThingConflictStrategy underTest = new ThingConflictStrategy(system);
         final ThingId thingId = ThingId.of("thing:id");
         final Thing thing = ThingsModelFactory.newThingBuilder().setId(thingId).setRevision(25L).build();
         final CommandStrategy.Context context = DefaultContext.getInstance(thingId,
diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/starter/ThingsServiceGlobalErrorRegistryTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/starter/ThingsServiceGlobalErrorRegistryTest.java
index 2923fe2644..21a8a341c7 100644
--- a/things/service/src/test/java/org/eclipse/ditto/things/service/starter/ThingsServiceGlobalErrorRegistryTest.java
+++ b/things/service/src/test/java/org/eclipse/ditto/things/service/starter/ThingsServiceGlobalErrorRegistryTest.java
@@ -35,6 +35,7 @@
 import org.eclipse.ditto.thingsearch.api.QueryTimeExceededException;
 import org.eclipse.ditto.thingsearch.model.signals.commands.exceptions.InvalidOptionException;
 import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
+import org.eclipse.ditto.wot.validation.WotThingModelPayloadValidationException;
 
 public final class ThingsServiceGlobalErrorRegistryTest extends GlobalErrorRegistryTestCases {
 
@@ -60,7 +61,8 @@ public ThingsServiceGlobalErrorRegistryTest() {
                 PathUnknownException.class,
                 WotThingModelInvalidException.class,
                 InvalidOptionException.class,
-                QueryTimeExceededException.class
+                QueryTimeExceededException.class,
+                WotThingModelPayloadValidationException.class
         );
 
     }
diff --git a/thingsearch/service/src/main/resources/search.conf b/thingsearch/service/src/main/resources/search.conf
index 945f66f6f0..c19d3dfe85 100755
--- a/thingsearch/service/src/main/resources/search.conf
+++ b/thingsearch/service/src/main/resources/search.conf
@@ -362,12 +362,12 @@ pekko {
 search-dispatcher {
   # one thread per query and a dedicated thread for the search actor
   type = PinnedDispatcher
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
 }
 
 blocked-namespaces-dispatcher {
   type = Dispatcher
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedForkJoinExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
   fork-join-executor {
     # Min number of threads to cap factor-based parallelism number to
     parallelism-min = 4
@@ -382,12 +382,12 @@ blocked-namespaces-dispatcher {
 
 policy-enforcer-cache-dispatcher {
   type = Dispatcher
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
 }
 
 thing-cache-dispatcher {
   type = Dispatcher
-  executor = "org.eclipse.ditto.internal.utils.metrics.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
+  executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
 }
 
 include "search-extension.conf"
diff --git a/wot/api/pom.xml b/wot/api/pom.xml
new file mode 100755
index 0000000000..1a486e08e7
--- /dev/null
+++ b/wot/api/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+    4.0.0
+
+    
+        org.eclipse.ditto
+        ditto-wot
+        ${revision}
+    
+
+    ditto-wot-api
+    Eclipse Ditto :: WoT :: API
+
+    
+        
+            org.eclipse.ditto
+            ditto-json
+        
+        
+            org.eclipse.ditto
+            ditto-base-model
+        
+        
+            org.eclipse.ditto
+            ditto-wot-model
+        
+        
+            org.eclipse.ditto
+            ditto-wot-validation
+        
+        
+            org.eclipse.ditto
+            ditto-things-model
+        
+
+        
+            org.eclipse.ditto
+            ditto-internal-utils-cache
+        
+    
+
+    
+        
+            
+                org.apache.maven.plugins
+                maven-enforcer-plugin
+                
+                    
+                        enforce-banned-dependencies
+                        
+                            enforce
+                        
+                        
+                            
+                                
+                                    
+                                        
+                                        org.apache.pekko
+                                    
+                                
+                            
+                            true
+                        
+                    
+                
+            
+        
+    
+
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java
new file mode 100644
index 0000000000..63f131bf19
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.config;
+
+import java.util.Objects;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.ConfigWithFallback;
+import org.eclipse.ditto.internal.utils.config.ScopedConfig;
+import org.eclipse.ditto.wot.validation.config.FeatureValidationConfig;
+
+import com.typesafe.config.Config;
+
+/**
+ * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.FeatureValidationConfig}.
+ */
+@Immutable
+final class DefaultFeatureValidationConfig implements FeatureValidationConfig {
+
+    private static final String CONFIG_PATH = "feature";
+
+    private final boolean enforceProperties;
+    private final boolean allowNonModeledProperties;
+    private final boolean enforceDesiredProperties;
+    private final boolean allowNonModeledDesiredProperties;
+    private final boolean enforceInboxMessages;
+    private final boolean allowNonModeledInboxMessages;
+    private final boolean enforceOutboxMessages;
+    private final boolean allowNonModeledOutboxMessages;
+
+    private DefaultFeatureValidationConfig(final ScopedConfig scopedConfig) {
+        enforceProperties =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_PROPERTIES.getConfigPath());
+        allowNonModeledProperties =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_PROPERTIES.getConfigPath());
+        enforceDesiredProperties =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_DESIRED_PROPERTIES.getConfigPath());
+        allowNonModeledDesiredProperties =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_DESIRED_PROPERTIES.getConfigPath());
+        enforceInboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_INBOX_MESSAGES.getConfigPath());
+        allowNonModeledInboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_INBOX_MESSAGES.getConfigPath());
+        enforceOutboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_OUTBOX_MESSAGES.getConfigPath());
+        allowNonModeledOutboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_OUTBOX_MESSAGES.getConfigPath());
+    }
+
+    /**
+     * Returns an instance of the thing config based on the settings of the specified Config.
+     *
+     * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}.
+     * @return the instance.
+     * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid.
+     */
+    public static DefaultFeatureValidationConfig of(final Config config) {
+        return new DefaultFeatureValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH,
+                ConfigValue.values()));
+    }
+
+    @Override
+    public boolean isEnforceProperties() {
+        return enforceProperties;
+    }
+
+    @Override
+    public boolean isAllowNonModeledProperties() {
+        return allowNonModeledProperties;
+    }
+
+    @Override
+    public boolean isEnforceDesiredProperties() {
+        return enforceDesiredProperties;
+    }
+
+    @Override
+    public boolean isAllowNonModeledDesiredProperties() {
+        return allowNonModeledDesiredProperties;
+    }
+
+    @Override
+    public boolean isEnforceInboxMessages() {
+        return enforceInboxMessages;
+    }
+
+    @Override
+    public boolean isAllowNonModeledInboxMessages() {
+        return allowNonModeledInboxMessages;
+    }
+
+    @Override
+    public boolean isEnforceOutboxMessages() {
+        return enforceOutboxMessages;
+    }
+
+    @Override
+    public boolean isAllowNonModeledOutboxMessages() {
+        return allowNonModeledOutboxMessages;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final DefaultFeatureValidationConfig that = (DefaultFeatureValidationConfig) o;
+        return enforceProperties == that.enforceProperties &&
+                allowNonModeledProperties == that.allowNonModeledProperties &&
+                enforceDesiredProperties == that.enforceDesiredProperties &&
+                allowNonModeledDesiredProperties == that.allowNonModeledDesiredProperties &&
+                enforceInboxMessages == that.enforceInboxMessages &&
+                allowNonModeledInboxMessages == that.allowNonModeledInboxMessages &&
+                enforceOutboxMessages == that.enforceOutboxMessages &&
+                allowNonModeledOutboxMessages == that.allowNonModeledOutboxMessages;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(enforceProperties, allowNonModeledProperties, enforceDesiredProperties,
+                allowNonModeledDesiredProperties, enforceInboxMessages, allowNonModeledInboxMessages,
+                enforceOutboxMessages, allowNonModeledOutboxMessages);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" +
+                "enforceProperties=" + enforceProperties +
+                ", allowNonModeledProperties=" + allowNonModeledProperties +
+                ", enforceDesiredProperties=" + enforceDesiredProperties +
+                ", allowNonModeledDesiredProperties=" + allowNonModeledDesiredProperties +
+                ", enforceInboxMessages=" + enforceInboxMessages +
+                ", allowNonModeledInboxMessages=" + allowNonModeledInboxMessages +
+                ", enforceOutboxMessages=" + enforceOutboxMessages +
+                ", allowNonModeledOutboxMessages=" + allowNonModeledOutboxMessages +
+                "]";
+    }
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java
new file mode 100644
index 0000000000..cf04dc4358
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.config;
+
+import java.util.Objects;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.ConfigWithFallback;
+import org.eclipse.ditto.internal.utils.config.ScopedConfig;
+import org.eclipse.ditto.wot.validation.config.ThingValidationConfig;
+
+import com.typesafe.config.Config;
+
+/**
+ * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.ThingValidationConfig}.
+ */
+@Immutable
+final class DefaultThingValidationConfig implements ThingValidationConfig {
+
+    private static final String CONFIG_PATH = "thing";
+
+    private final boolean enforceAttributes;
+    private final boolean allowNonModeledAttributes;
+    private final boolean enforceInboxMessages;
+    private final boolean allowNonModeledInboxMessages;
+    private final boolean enforceOutboxMessages;
+    private final boolean allowNonModeledOutboxMessages;
+
+    private DefaultThingValidationConfig(final ScopedConfig scopedConfig) {
+        enforceAttributes =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_ATTRIBUTES.getConfigPath());
+        allowNonModeledAttributes =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_ATTRIBUTES.getConfigPath());
+        enforceInboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_INBOX_MESSAGES.getConfigPath());
+        allowNonModeledInboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_INBOX_MESSAGES.getConfigPath());
+        enforceOutboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ENFORCE_OUTBOX_MESSAGES.getConfigPath());
+        allowNonModeledOutboxMessages =
+                scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_OUTBOX_MESSAGES.getConfigPath());
+    }
+
+    /**
+     * Returns an instance of the thing config based on the settings of the specified Config.
+     *
+     * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}.
+     * @return the instance.
+     * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid.
+     */
+    public static DefaultThingValidationConfig of(final Config config) {
+        return new DefaultThingValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH,
+                ConfigValue.values()));
+    }
+
+    @Override
+    public boolean isEnforceAttributes() {
+        return enforceAttributes;
+    }
+
+    @Override
+    public boolean isAllowNonModeledAttributes() {
+        return allowNonModeledAttributes;
+    }
+
+    @Override
+    public boolean isEnforceInboxMessages() {
+        return enforceInboxMessages;
+    }
+
+    @Override
+    public boolean isAllowNonModeledInboxMessages() {
+        return allowNonModeledInboxMessages;
+    }
+
+    @Override
+    public boolean isEnforceOutboxMessages() {
+        return enforceOutboxMessages;
+    }
+
+    @Override
+    public boolean isAllowNonModeledOutboxMessages() {
+        return allowNonModeledOutboxMessages;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final DefaultThingValidationConfig that = (DefaultThingValidationConfig) o;
+        return enforceAttributes == that.enforceAttributes &&
+                allowNonModeledAttributes == that.allowNonModeledAttributes &&
+                enforceInboxMessages == that.enforceInboxMessages &&
+                allowNonModeledInboxMessages == that.allowNonModeledInboxMessages &&
+                enforceOutboxMessages == that.enforceOutboxMessages &&
+                allowNonModeledOutboxMessages == that.allowNonModeledOutboxMessages;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(enforceAttributes, allowNonModeledAttributes, enforceInboxMessages,
+                allowNonModeledInboxMessages, enforceOutboxMessages, allowNonModeledOutboxMessages);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" +
+                "enforceAttributes=" + enforceAttributes +
+                ", allowNonModeledAttributes=" + allowNonModeledAttributes +
+                ", enforceInboxMessages=" + enforceInboxMessages +
+                ", allowNonModeledInboxMessages=" + allowNonModeledInboxMessages +
+                ", enforceOutboxMessages=" + enforceOutboxMessages +
+                ", allowNonModeledOutboxMessages=" + allowNonModeledOutboxMessages +
+                "]";
+    }
+}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java
similarity index 96%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java
index 1039a202ef..25cefa6b87 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import java.util.Objects;
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java
similarity index 96%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java
index f56139c3a0..4f0640ea0e 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import java.util.Objects;
 
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java
new file mode 100644
index 0000000000..1c08282626
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.config;
+
+import java.util.Objects;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.ConfigWithFallback;
+import org.eclipse.ditto.internal.utils.config.ScopedConfig;
+import org.eclipse.ditto.wot.validation.config.FeatureValidationConfig;
+import org.eclipse.ditto.wot.validation.config.ThingValidationConfig;
+import org.eclipse.ditto.wot.validation.config.TmValidationConfig;
+
+import com.typesafe.config.Config;
+
+/**
+ * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.TmValidationConfig}.
+ */
+@Immutable
+final class DefaultTmValidationConfig implements TmValidationConfig {
+
+    private static final String CONFIG_PATH = "tm-model-validation";
+
+    private final boolean enabled;
+    private final ThingValidationConfig thingValidationConfig;
+    private final FeatureValidationConfig featureValidationConfig;
+
+    private DefaultTmValidationConfig(final ScopedConfig scopedConfig) {
+        enabled = scopedConfig.getBoolean(ConfigValue.ENABLED.getConfigPath());
+        thingValidationConfig = DefaultThingValidationConfig.of(scopedConfig);
+        featureValidationConfig = DefaultFeatureValidationConfig.of(scopedConfig);
+    }
+
+    /**
+     * Returns an instance of the thing config based on the settings of the specified Config.
+     *
+     * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}.
+     * @return the instance.
+     * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid.
+     */
+    public static DefaultTmValidationConfig of(final Config config) {
+        return new DefaultTmValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH,
+                ConfigValue.values()));
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public ThingValidationConfig getThingValidationConfig() {
+        return thingValidationConfig;
+    }
+
+    @Override
+    public FeatureValidationConfig getFeatureValidationConfig() {
+        return featureValidationConfig;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final DefaultTmValidationConfig that = (DefaultTmValidationConfig) o;
+        return enabled == that.enabled &&
+                Objects.equals(thingValidationConfig, that.thingValidationConfig) &&
+                Objects.equals(featureValidationConfig, that.featureValidationConfig);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(enabled, thingValidationConfig, featureValidationConfig);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" +
+                "enabled=" + enabled +
+                ", thingValidationConfig=" + thingValidationConfig +
+                ", featureValidationConfig=" + featureValidationConfig +
+                "]";
+    }
+}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java
similarity index 97%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java
index 172775dbd3..92582a4d4c 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import java.util.Map;
 import java.util.Objects;
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java
similarity index 74%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java
index 052dc3a7a5..63c8ac0764 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,18 +10,19 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import java.util.Objects;
 
 import javax.annotation.concurrent.Immutable;
 
-import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig;
-import org.eclipse.ditto.base.service.config.http.HttpProxyConfig;
 import org.eclipse.ditto.internal.utils.cache.config.CacheConfig;
 import org.eclipse.ditto.internal.utils.cache.config.DefaultCacheConfig;
 import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
 import org.eclipse.ditto.internal.utils.config.ScopedConfig;
+import org.eclipse.ditto.internal.utils.config.http.DefaultHttpProxyBaseConfig;
+import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig;
+import org.eclipse.ditto.wot.validation.config.TmValidationConfig;
 
 import com.typesafe.config.Config;
 
@@ -33,18 +34,25 @@
 @Immutable
 public final class DefaultWotConfig implements WotConfig {
 
+    /**
+     * The parent path of the "wot" config.
+     */
+    public static final String WOT_PARENT_CONFIG_PATH = "things";
+
     private static final String CONFIG_PATH = "wot";
 
-    private final HttpProxyConfig httpProxyConfig;
+    private final HttpProxyBaseConfig httpProxyConfig;
     private final CacheConfig cacheConfig;
     private final ToThingDescriptionConfig toThingDescriptionConfig;
-    private final DefaultTmBasedCreationConfig tmBasedCreationConfig;
+    private final TmBasedCreationConfig tmBasedCreationConfig;
+    private final TmValidationConfig tmValidationConfig;
 
     private DefaultWotConfig(final ScopedConfig scopedConfig) {
-        httpProxyConfig = DefaultHttpProxyConfig.ofHttpProxy(scopedConfig);
+        httpProxyConfig = DefaultHttpProxyBaseConfig.ofHttpProxy(scopedConfig);
         cacheConfig = DefaultCacheConfig.of(scopedConfig, "cache");
         toThingDescriptionConfig = DefaultToThingDescriptionConfig.of(scopedConfig);
         tmBasedCreationConfig = DefaultTmBasedCreationConfig.of(scopedConfig);
+        tmValidationConfig = DefaultTmValidationConfig.of(scopedConfig);
     }
 
     /**
@@ -59,7 +67,7 @@ public static DefaultWotConfig of(final Config config) {
     }
 
     @Override
-    public HttpProxyConfig getHttpProxyConfig() {
+    public HttpProxyBaseConfig getHttpProxyConfig() {
         return httpProxyConfig;
     }
 
@@ -78,6 +86,11 @@ public TmBasedCreationConfig getCreationConfig() {
         return tmBasedCreationConfig;
     }
 
+    @Override
+    public TmValidationConfig getValidationConfig() {
+        return tmValidationConfig;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) {
@@ -90,12 +103,13 @@ public boolean equals(final Object o) {
         return Objects.equals(httpProxyConfig, that.httpProxyConfig) &&
                 Objects.equals(cacheConfig, that.cacheConfig) &&
                 Objects.equals(toThingDescriptionConfig, that.toThingDescriptionConfig) &&
-                Objects.equals(tmBasedCreationConfig, that.tmBasedCreationConfig);
+                Objects.equals(tmBasedCreationConfig, that.tmBasedCreationConfig) &&
+                Objects.equals(tmValidationConfig, that.tmValidationConfig);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(httpProxyConfig, cacheConfig, toThingDescriptionConfig, tmBasedCreationConfig);
+        return Objects.hash(httpProxyConfig, cacheConfig, toThingDescriptionConfig, tmBasedCreationConfig, tmValidationConfig);
     }
 
     @Override
@@ -105,6 +119,7 @@ public String toString() {
                 ", cacheConfig=" + cacheConfig +
                 ", toThingDescriptionConfig=" + toThingDescriptionConfig +
                 ", tmBasedCreationConfig=" + tmBasedCreationConfig +
+                ", tmValidationConfig=" + tmValidationConfig +
                 "]";
     }
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java
similarity index 94%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java
index 564bb98c78..a4754ebb4f 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import javax.annotation.concurrent.Immutable;
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java
similarity index 96%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java
index 9735b76808..bdd1cb6f71 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import javax.annotation.concurrent.Immutable;
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java
similarity index 96%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java
index 9f591aebb6..d68183cc88 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import java.util.Map;
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java
similarity index 70%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java
index c36b357efd..3b64d290c8 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,12 +10,13 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.config;
+package org.eclipse.ditto.wot.api.config;
 
 import javax.annotation.concurrent.Immutable;
 
-import org.eclipse.ditto.base.service.config.http.HttpProxyConfig;
 import org.eclipse.ditto.internal.utils.cache.config.CacheConfig;
+import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig;
+import org.eclipse.ditto.wot.validation.config.TmValidationConfig;
 
 /**
  * Provides configuration settings for WoT (Web of Things) integration.
@@ -30,7 +31,7 @@ public interface WotConfig {
      *
      * @return configuration settings for the HTTP proxy.
      */
-    HttpProxyConfig getHttpProxyConfig();
+    HttpProxyBaseConfig getHttpProxyConfig();
 
     /**
      * Returns the cache configuration to apply for caching downloaded WoT Thing Models.
@@ -50,8 +51,15 @@ public interface WotConfig {
     /**
      * Returns configuration for WoT TM (ThingModel) based creation of Things and Features.
      *
-     * @return  configuration for WoT TM (ThingModel) based creation of Things and Features.
+     * @return configuration for WoT TM (ThingModel) based creation of Things and Features.
      */
     TmBasedCreationConfig getCreationConfig();
 
+    /**
+     * @return configuration for WoT (Web of Things) integration regarding the validation of Things and Features
+     * based on their WoT ThingModels.
+     * @since 3.6.0
+     */
+    TmValidationConfig getValidationConfig();
+
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java
similarity index 80%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java
index c8a84b7185..92fb86e927 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -13,7 +13,7 @@
 
 /**
  * Configuration for the WoT (Web of Things) integration.
- * @since 2.4.0
+ * @since 3.6.0
  */
 @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
-package org.eclipse.ditto.wot.integration.config;
\ No newline at end of file
+package org.eclipse.ditto.wot.api.config;
\ No newline at end of file
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java
similarity index 86%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java
index 0cc272c70d..174669c83d 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
 
@@ -23,7 +23,9 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
@@ -35,13 +37,10 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaderDefinition;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.base.model.headers.contenttype.ContentType;
-import org.eclipse.ditto.internal.utils.pekko.logging.DittoLogger;
-import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
 import org.eclipse.ditto.json.JsonArray;
 import org.eclipse.ditto.json.JsonCollectors;
 import org.eclipse.ditto.json.JsonField;
@@ -49,12 +48,15 @@
 import org.eclipse.ditto.json.JsonObject;
 import org.eclipse.ditto.json.JsonPointer;
 import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.things.model.DefinitionIdentifier;
 import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.FeatureDefinition;
 import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.things.model.ThingDefinition;
 import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.wot.integration.config.ToThingDescriptionConfig;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.config.ToThingDescriptionConfig;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
 import org.eclipse.ditto.wot.model.Action;
 import org.eclipse.ditto.wot.model.ActionFormElement;
 import org.eclipse.ditto.wot.model.ActionForms;
@@ -91,13 +93,17 @@
 import org.eclipse.ditto.wot.model.SinglePropertyFormElementOp;
 import org.eclipse.ditto.wot.model.SingleRootFormElementOp;
 import org.eclipse.ditto.wot.model.StringSchema;
+import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
 import org.eclipse.ditto.wot.model.ThingDescription;
 import org.eclipse.ditto.wot.model.ThingModel;
 import org.eclipse.ditto.wot.model.Title;
 import org.eclipse.ditto.wot.model.UriVariables;
 import org.eclipse.ditto.wot.model.Version;
+import org.eclipse.ditto.wot.model.WotInternalErrorException;
 import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
 import org.eclipse.ditto.wot.model.WotThingModelPlaceholderUnresolvedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default Ditto specific implementation of {@link WotThingDescriptionGenerator}.
@@ -105,8 +111,7 @@
 @Immutable
 final class DefaultWotThingDescriptionGenerator implements WotThingDescriptionGenerator {
 
-    private static final DittoLogger LOGGER =
-            DittoLoggerFactory.getLogger(DefaultWotThingDescriptionGenerator.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingDescriptionGenerator.class);
 
     private static final String TM_PLACEHOLDER_PL_GROUP = "pl";
     private static final Pattern TM_PLACEHOLDER_PATTERN =
@@ -123,15 +128,134 @@ final class DefaultWotThingDescriptionGenerator implements WotThingDescriptionGe
     private static final String SCHEMA_DITTO_ERROR = "dittoError";
     private static final String DITTO_FIELDS_URI_VARIABLE = "fields";
 
+    private static final String MODEL_PLACEHOLDERS_KEY = "model-placeholders";
+
     private final ToThingDescriptionConfig toThingDescriptionConfig;
-    private final WotThingModelExtensionResolver thingModelExtensionResolver;
+    private final WotThingModelResolver thingModelResolver;
+    private final Executor executor;
 
-    DefaultWotThingDescriptionGenerator(final ActorSystem actorSystem,
+    DefaultWotThingDescriptionGenerator(
             final WotConfig wotConfig,
-            final WotThingModelFetcher thingModelFetcher) {
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
         this.toThingDescriptionConfig = checkNotNull(wotConfig, "wotConfig").getToThingDescriptionConfig();
-        thingModelExtensionResolver = new DefaultWotThingModelExtensionResolver(thingModelFetcher,
-                actorSystem.dispatchers().lookup("wot-dispatcher"));
+        this.thingModelResolver = checkNotNull(thingModelResolver, "thingModelResolver");
+        this.executor = executor;
+    }
+
+    @Override
+    public CompletionStage provideThingTD(@Nullable final ThingDefinition thingDefinition,
+            final ThingId thingId,
+            @Nullable final Thing thing,
+            final DittoHeaders dittoHeaders) {
+        if (null != thingDefinition) {
+            return getWotThingDescriptionForThing(thingDefinition, thingId, thing, dittoHeaders);
+        } else {
+            throw ThingDefinitionInvalidException.newBuilder(null)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    @Override
+    public CompletionStage provideFeatureTD(final ThingId thingId,
+            @Nullable final Thing thing,
+            final Feature feature,
+            final DittoHeaders dittoHeaders) {
+
+        checkNotNull(feature, "feature");
+        if (feature.getDefinition().isPresent()) {
+            return getWotThingDescriptionForFeature(thingId, thing, feature, dittoHeaders);
+        } else {
+            throw ThingDefinitionInvalidException.newBuilder(null)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+
+
+    /**
+     * Download TM, add it to local cache + build TD + return it
+     */
+    private CompletionStage getWotThingDescriptionForThing(final ThingDefinition definitionIdentifier,
+            final ThingId thingId,
+            @Nullable final Thing thing,
+            final DittoHeaders dittoHeaders) {
+
+        final Optional urlOpt = definitionIdentifier.getUrl();
+        if (urlOpt.isPresent()) {
+            final URL url = urlOpt.get();
+            return thingModelResolver.resolveThingModel(url, dittoHeaders)
+                    .thenComposeAsync(thingModel -> generateThingDescription(thingId,
+                                            thing,
+                                            Optional.ofNullable(thing)
+                                                    .flatMap(Thing::getAttributes)
+                                                    .flatMap(a -> a.getValue(MODEL_PLACEHOLDERS_KEY))
+                                                    .filter(JsonValue::isObject)
+                                                    .map(JsonValue::asObject)
+                                                    .orElse(null),
+                                            null,
+                                            thingModel,
+                                            url,
+                                            dittoHeaders
+                                    ),
+                            executor
+                    )
+                    .exceptionally(throwable -> {
+                        throw DittoRuntimeException.asDittoRuntimeException(throwable, t ->
+                                WotInternalErrorException.newBuilder()
+                                        .dittoHeaders(dittoHeaders)
+                                        .cause(t)
+                                        .build());
+                    });
+        } else {
+            throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    /**
+     * Download TM, add it to local cache + build TD + return it
+     */
+    private CompletionStage getWotThingDescriptionForFeature(final ThingId thingId,
+            @Nullable final Thing thing,
+            final Feature feature,
+            final DittoHeaders dittoHeaders) {
+
+        final Optional definitionIdentifier = feature.getDefinition()
+                .map(FeatureDefinition::getFirstIdentifier);
+        final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl);
+        if (urlOpt.isPresent()) {
+            final URL url = urlOpt.get();
+            return thingModelResolver.resolveThingModel(url, dittoHeaders)
+                    .thenComposeAsync(thingModel -> generateThingDescription(thingId,
+                                            thing,
+                                            feature.getProperties()
+                                                    .flatMap(p -> p.getValue(MODEL_PLACEHOLDERS_KEY))
+                                                    .filter(JsonValue::isObject)
+                                                    .map(JsonValue::asObject)
+                                                    .orElse(null),
+                                            feature.getId(),
+                                            thingModel,
+                                            url,
+                                            dittoHeaders
+                                    ),
+                            executor
+                    )
+                    .exceptionally(throwable -> {
+                        throw DittoRuntimeException.asDittoRuntimeException(throwable, t ->
+                                WotInternalErrorException.newBuilder()
+                                        .dittoHeaders(dittoHeaders)
+                                        .cause(t)
+                                        .build());
+                    });
+        } else {
+            throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier.orElse(null))
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
     }
 
     @Override
@@ -144,15 +268,10 @@ public CompletionStage generateThingDescription(final ThingId
             final DittoHeaders dittoHeaders) {
 
         // generation rules defined at: https://w3c.github.io/wot-thing-description/#thing-model-td-generation
-        return thingModelExtensionResolver
-                .resolveThingModelExtensions(thingModel, dittoHeaders)
-                .thenCompose(thingModelWithExtensions ->
-                        thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions, dittoHeaders)
-                )
+        return CompletableFuture.completedFuture(thingModel)
                 .thenApply(thingModelWithExtensionsAndImports -> {
-                    LOGGER.withCorrelationId(dittoHeaders)
-                            .debug("ThingModel after resolving extensions + refs: <{}>",
-                                    thingModelWithExtensionsAndImports);
+                    LOGGER.debug("ThingModel after resolving extensions + refs: <{}>",
+                            thingModelWithExtensionsAndImports);
 
                     final ThingModel cleanedTm =
                             removeThingModelSpecificElements(thingModelWithExtensionsAndImports, dittoHeaders);
@@ -183,10 +302,8 @@ public CompletionStage generateThingDescription(final ThingId
                     final ThingDescription thingDescription =
                             resolvePlaceholders(tdBuilder.build(), placeholderLookupObject,
                                     dittoHeaders);
-                    LOGGER.withCorrelationId(dittoHeaders)
-                            .info("Created ThingDescription for thingId <{}> and featureId <{}>: <{}>", thingId,
-                                    featureId,
-                                    thingDescription);
+                    LOGGER.info("Created ThingDescription for thingId <{}> and featureId <{}>: <{}>", thingId,
+                            featureId, thingDescription);
                     return thingDescription;
                 });
     }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java
similarity index 97%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java
index 631d2673d8..231c453e3c 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
 
@@ -27,7 +27,7 @@
 import org.eclipse.ditto.json.JsonObject;
 import org.eclipse.ditto.json.JsonPointer;
 import org.eclipse.ditto.json.JsonValue;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
 import org.eclipse.ditto.wot.model.IRI;
 import org.eclipse.ditto.wot.model.ThingModel;
 import org.eclipse.ditto.wot.model.WotThingModelRefInvalidException;
@@ -43,8 +43,7 @@ final class DefaultWotThingModelExtensionResolver implements WotThingModelExtens
     private final WotThingModelFetcher thingModelFetcher;
     private final Executor executor;
 
-    DefaultWotThingModelExtensionResolver(final WotThingModelFetcher thingModelFetcher,
-            final Executor executor) {
+    DefaultWotThingModelExtensionResolver(final WotThingModelFetcher thingModelFetcher, final Executor executor) {
         this.thingModelFetcher = checkNotNull(thingModelFetcher, "thingModelFetcher");
         this.executor = executor;
     }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java
similarity index 68%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java
index 32534431ff..9c76149d88 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,12 +10,13 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -27,16 +28,13 @@
 import java.util.concurrent.Executor;
 import java.util.function.Function;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
 
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import org.apache.pekko.actor.ActorSystem;
-import org.apache.pekko.japi.Pair;
+import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
-import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
+import org.eclipse.ditto.base.model.signals.FeatureToggle;
 import org.eclipse.ditto.json.JsonArray;
 import org.eclipse.ditto.json.JsonArrayBuilder;
 import org.eclipse.ditto.json.JsonObject;
@@ -54,9 +52,11 @@
 import org.eclipse.ditto.things.model.FeaturesBuilder;
 import org.eclipse.ditto.things.model.Thing;
 import org.eclipse.ditto.things.model.ThingBuilder;
+import org.eclipse.ditto.things.model.ThingDefinition;
 import org.eclipse.ditto.things.model.ThingId;
 import org.eclipse.ditto.things.model.ThingsModelFactory;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
 import org.eclipse.ditto.wot.model.ArraySchema;
 import org.eclipse.ditto.wot.model.BaseLink;
 import org.eclipse.ditto.wot.model.DataSchemaType;
@@ -71,7 +71,9 @@
 import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
 import org.eclipse.ditto.wot.model.ThingModel;
 import org.eclipse.ditto.wot.model.TmOptional;
-import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
+import org.eclipse.ditto.wot.model.WotInternalErrorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default Ditto specific implementation of {@link WotThingSkeletonGenerator}.
@@ -79,22 +81,131 @@
 @Immutable
 final class DefaultWotThingSkeletonGenerator implements WotThingSkeletonGenerator {
 
-    private static final ThreadSafeDittoLogger LOGGER =
-            DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingSkeletonGenerator.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingSkeletonGenerator.class);
 
     private static final String TM_EXTENDS = "tm:extends";
 
-    private static final String TM_SUBMODEL = "tm:submodel";
-    private static final String TM_SUBMODEL_INSTANCE_NAME = "instanceName";
-
-    private final WotThingModelFetcher thingModelFetcher;
+    private final WotConfig wotConfig;
+    private final WotThingModelResolver thingModelResolver;
     private final Executor executor;
-    private final WotThingModelExtensionResolver thingModelExtensionResolver;
 
-    DefaultWotThingSkeletonGenerator(final ActorSystem actorSystem, final WotThingModelFetcher thingModelFetcher) {
-        this.thingModelFetcher = checkNotNull(thingModelFetcher, "thingModelFetcher");
-        executor = actorSystem.dispatchers().lookup("wot-dispatcher");
-        thingModelExtensionResolver = new DefaultWotThingModelExtensionResolver(thingModelFetcher, executor);
+    DefaultWotThingSkeletonGenerator(final WotConfig wotConfig,
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
+        this.thingModelResolver = checkNotNull(thingModelResolver, "thingModelResolver");
+        this.wotConfig = checkNotNull(wotConfig, "wotConfig");
+        this.executor = executor;
+    }
+
+    @Override
+    public CompletionStage> provideThingSkeletonForCreation(final ThingId thingId,
+            @Nullable final ThingDefinition thingDefinition,
+            final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() &&
+                wotConfig.getCreationConfig().getThingCreationConfig().isSkeletonCreationEnabled() &&
+                null != thingDefinition) {
+            final Optional urlOpt = thingDefinition.getUrl();
+            if (urlOpt.isPresent()) {
+                final URL url = urlOpt.get();
+                LOGGER.debug("Resolving ThingModel from <{}> in order to create Thing skeleton for new Thing " +
+                        "with id <{}>", url, thingId);
+                return thingModelResolver.resolveThingModel(url, dittoHeaders)
+                        .thenComposeAsync(thingModel -> generateThingSkeleton(
+                                        thingId,
+                                        thingModel,
+                                        url,
+                                        wotConfig.getCreationConfig()
+                                                .getThingCreationConfig()
+                                                .shouldGenerateDefaultsForOptionalProperties(),
+                                        dittoHeaders
+                                ),
+                                executor
+                        )
+                        .handle((thingSkeleton, throwable) -> {
+                            if (throwable != null) {
+                                LOGGER.info("Could not fetch ThingModel or generate Thing skeleton based on it due " +
+                                                "to: <{}: {}>",
+                                        throwable.getClass().getSimpleName(), throwable.getMessage(), throwable);
+                                if (wotConfig.getCreationConfig()
+                                        .getThingCreationConfig()
+                                        .shouldThrowExceptionOnWotErrors()) {
+                                    throw DittoRuntimeException.asDittoRuntimeException(
+                                            throwable, t -> WotInternalErrorException.newBuilder()
+                                                    .dittoHeaders(dittoHeaders)
+                                                    .cause(t)
+                                                    .build()
+                                    );
+                                } else {
+                                    return Optional.empty();
+                                }
+                            } else {
+                                LOGGER.debug("Created Thing skeleton for new Thing with id <{}>: <{}>", thingId,
+                                        thingSkeleton);
+                                return thingSkeleton;
+                            }
+                        });
+            } else {
+                return CompletableFuture.completedFuture(Optional.empty());
+            }
+        } else {
+            return CompletableFuture.completedFuture(Optional.empty());
+        }
+    }
+
+    @Override
+    public CompletionStage> provideFeatureSkeletonForCreation(final String featureId,
+            @Nullable final FeatureDefinition featureDefinition, final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() &&
+                wotConfig.getCreationConfig().getFeatureCreationConfig().isSkeletonCreationEnabled() &&
+                null != featureDefinition) {
+            final Optional urlOpt = featureDefinition.getFirstIdentifier().getUrl();
+            if (urlOpt.isPresent()) {
+                final URL url = urlOpt.get();
+                LOGGER.debug("Resolving ThingModel from <{}> in order to create Feature skeleton for new Feature " +
+                        "with id <{}>", url, featureId);
+                return thingModelResolver.resolveThingModel(url, dittoHeaders)
+                        .thenComposeAsync(thingModel -> generateFeatureSkeleton(
+                                        featureId,
+                                        thingModel,
+                                        url,
+                                        wotConfig.getCreationConfig()
+                                                .getFeatureCreationConfig()
+                                                .shouldGenerateDefaultsForOptionalProperties(),
+                                        dittoHeaders
+                                ),
+                                executor
+                        )
+                        .handle((featureSkeleton, throwable) -> {
+                            if (throwable != null) {
+                                LOGGER.info("Could not fetch ThingModel or generate Feature skeleton based on it due " +
+                                                "to: <{}: {}>",
+                                        throwable.getClass().getSimpleName(), throwable.getMessage(), throwable);
+                                if (wotConfig.getCreationConfig()
+                                        .getFeatureCreationConfig()
+                                        .shouldThrowExceptionOnWotErrors()) {
+                                    throw DittoRuntimeException.asDittoRuntimeException(
+                                            throwable, t -> WotInternalErrorException.newBuilder()
+                                                    .dittoHeaders(dittoHeaders)
+                                                    .cause(t)
+                                                    .build()
+                                    );
+                                } else {
+                                    return Optional.empty();
+                                }
+                            } else {
+                                LOGGER.debug("Created Feature skeleton for new Feature with id <{}>: <{}>", featureId,
+                                        featureSkeleton);
+                                return featureSkeleton;
+                            }
+                        });
+            } else {
+                return CompletableFuture.completedFuture(Optional.empty());
+            }
+        } else {
+            return CompletableFuture.completedFuture(Optional.empty());
+        }
     }
 
     @Override
@@ -104,18 +215,13 @@ public CompletionStage> generateThingSkeleton(final ThingId thin
             final boolean generateDefaultsForOptionalProperties,
             final DittoHeaders dittoHeaders) {
 
-        return thingModelExtensionResolver
-                .resolveThingModelExtensions(thingModel, dittoHeaders)
-                .thenCompose(thingModelWithExtensions ->
-                        thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions, dittoHeaders)
-                )
+        return CompletableFuture.completedFuture(thingModel)
                 .thenApply(thingModelWithExtensionsAndImports -> {
                     final Optional dittoExtensionPrefix = thingModelWithExtensionsAndImports.getAtContext()
                             .determinePrefixFor(DittoWotExtension.DITTO_WOT_EXTENSION);
 
-                    LOGGER.withCorrelationId(dittoHeaders)
-                            .debug("ThingModel for generating Thing skeleton after resolving extensions + refs: <{}>",
-                                    thingModelWithExtensionsAndImports);
+                    LOGGER.debug("ThingModel for generating Thing skeleton after resolving extensions + refs: <{}>",
+                            thingModelWithExtensionsAndImports);
 
                     final ThingBuilder.FromScratch builder = Thing.newBuilder();
                     thingModelWithExtensionsAndImports.getProperties()
@@ -148,12 +254,12 @@ public CompletionStage> generateThingSkeleton(final ThingId thin
                                 return attributesBuilder.build();
                             }).ifPresent(builder::setAttributes);
 
-                    return Pair.apply(thingModelWithExtensionsAndImports, builder);
+                    return new AbstractMap.SimpleImmutableEntry<>(thingModelWithExtensionsAndImports, builder);
                 })
                 .thenCompose(pair ->
-                        createFeaturesFromSubmodels(pair.first(), generateDefaultsForOptionalProperties, dittoHeaders)
+                        createFeaturesFromSubmodels(pair.getKey(), generateDefaultsForOptionalProperties, dittoHeaders)
                                 .thenApply(features ->
-                                        features.map(f -> pair.second().setFeatures(f)).orElse(pair.second())
+                                        features.map(f -> pair.getValue().setFeatures(f)).orElse(pair.getValue())
                                 )
                 )
                 .thenApply(builder -> Optional.of(builder.build()));
@@ -195,55 +301,35 @@ private static void fillPropertiesInOptionalCategories(final Properties properti
     private CompletionStage> createFeaturesFromSubmodels(final ThingModel thingModel,
             final boolean generateDefaultsForOptionalProperties, final DittoHeaders dittoHeaders) {
 
-        final FeaturesBuilder featuresBuilder = Features.newBuilder();
-        final List>> futureList = thingModel.getLinks()
-                .map(links -> links.stream()
-                        .filter(baseLink -> baseLink.getRel().filter(TM_SUBMODEL::equals).isPresent())
-                        .map(baseLink -> {
-                                    final String instanceName = baseLink.getValue(TM_SUBMODEL_INSTANCE_NAME)
-                                            .filter(JsonValue::isString)
-                                            .map(JsonValue::asString)
-                                            .orElseThrow(() -> WotThingModelInvalidException
-                                                    .newBuilder("The required 'instanceName' field of the " +
-                                                            "'tm:submodel' link was not provided."
-                                                    ).dittoHeaders(dittoHeaders)
-                                                    .build()
-                                            );
-                                    LOGGER.withCorrelationId(dittoHeaders)
-                                            .debug("Resolved TM submodel with instanceName <{}> and href <{}>",
-                                                    instanceName, baseLink.getHref());
-                                    return new Submodel(instanceName, baseLink.getHref());
-                                }
-                        )
-                )
-                .orElseGet(Stream::empty)
-                .map(submodel -> thingModelFetcher.fetchThingModel(submodel.href, dittoHeaders)
-                        .thenComposeAsync(subThingModel ->
-                                generateFeatureSkeleton(submodel.instanceName,
-                                        subThingModel,
-                                        submodel.href,
-                                        generateDefaultsForOptionalProperties,
-                                        dittoHeaders
-                                ), executor)
-                        .toCompletableFuture()
-                )
-                .toList();
+        final CompletionStage>>> futureListStage =
+                thingModelResolver.resolveThingModelSubmodels(thingModel, dittoHeaders)
+                        .thenApplyAsync(submodelMap -> submodelMap.entrySet().stream()
+                                        .map(entry -> generateFeatureSkeleton(entry.getKey().instanceName(),
+                                                entry.getValue(),
+                                                entry.getKey().href(),
+                                                generateDefaultsForOptionalProperties,
+                                                dittoHeaders
+                                        ).toCompletableFuture())
+                                        .toList()
+                                , executor);
 
-        return CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
-                .thenApplyAsync(v -> {
-                            if (futureList.isEmpty()) {
-                                return Optional.empty();
-                            } else {
-                                featuresBuilder.setAll(futureList.stream()
-                                        .map(CompletableFuture::join)
-                                        .filter(Optional::isPresent)
-                                        .map(Optional::get)
-                                        .toList());
-                                return Optional.of(featuresBuilder.build());
-                            }
-                        },
-                        executor
-                );
+        final FeaturesBuilder featuresBuilder = Features.newBuilder();
+        return futureListStage.thenCompose(futureList ->
+                CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
+                        .thenApplyAsync(v -> {
+                                    if (futureList.isEmpty()) {
+                                        return Optional.empty();
+                                    } else {
+                                        featuresBuilder.setAll(futureList.stream()
+                                                .map(CompletableFuture::join)
+                                                .filter(Optional::isPresent)
+                                                .map(Optional::get)
+                                                .toList());
+                                        return Optional.of(featuresBuilder.build());
+                                    }
+                                },
+                                executor
+                        ));
     }
 
     private CompletionStage> generateFeatureSkeleton(final String featureId,
@@ -268,19 +354,16 @@ public CompletionStage> generateFeatureSkeleton(final String f
             final boolean generateDefaultsForOptionalProperties,
             final DittoHeaders dittoHeaders) {
 
-        return thingModelExtensionResolver
-                .resolveThingModelExtensions(thingModel, dittoHeaders)
-                .thenCompose(thingModelWithExtensions -> thingModelExtensionResolver
-                        .resolveThingModelRefs(thingModelWithExtensions, dittoHeaders))
+        return CompletableFuture.completedFuture(thingModel)
                 .thenCombine(resolveFeatureDefinition(thingModel, thingModelUrl, dittoHeaders),
                         (thingModelWithExtensionsAndImports, featureDefinition) -> {
                             final Optional dittoExtensionPrefix =
                                     thingModelWithExtensionsAndImports.getAtContext()
                                             .determinePrefixFor(DittoWotExtension.DITTO_WOT_EXTENSION);
 
-                            LOGGER.withCorrelationId(dittoHeaders)
-                                    .debug("ThingModel for generating Feature skeleton after resolving extensions + refs: <{}>",
-                                            thingModelWithExtensionsAndImports);
+                            LOGGER.debug(
+                                    "ThingModel for generating Feature skeleton after resolving extensions + refs: <{}>",
+                                    thingModelWithExtensionsAndImports);
 
                             final FeatureBuilder.FromScratchBuildable builder = Feature.newBuilder();
                             thingModelWithExtensionsAndImports.getProperties()
@@ -493,7 +576,7 @@ private CompletionStage> determineFurtherFeatureDefin
 
             if (extendsLink.isPresent()) {
                 final BaseLink link = extendsLink.get();
-                return thingModelFetcher.fetchThingModel(link.getHref(), dittoHeaders)
+                return thingModelResolver.resolveThingModel(link.getHref(), dittoHeaders)
                         .thenComposeAsync(subThingModel ->
                                 determineFurtherFeatureDefinitionIdentifiers( // recurse!
                                         subThingModel,
@@ -512,14 +595,4 @@ private CompletionStage> determineFurtherFeatureDefin
         }).orElseGet(() -> CompletableFuture.completedFuture(Collections.emptyList()));
     }
 
-    private static class Submodel {
-
-        private final String instanceName;
-        private final IRI href;
-
-        public Submodel(final String instanceName, final IRI href) {
-            this.instanceName = instanceName;
-            this.href = href;
-        }
-    }
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java
similarity index 91%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java
index 26453f567b..68a9fcfef5 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,7 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import org.eclipse.ditto.wot.model.SingleUriAtContext;
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java
similarity index 50%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java
index 3b7a7c83be..706d25dcdd 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,34 +10,32 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.provider;
+package org.eclipse.ditto.wot.api.generator;
 
-import java.util.Optional;
+import java.net.URL;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
 
 import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
 
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.json.JsonObject;
 import org.eclipse.ditto.things.model.Feature;
-import org.eclipse.ditto.things.model.FeatureDefinition;
 import org.eclipse.ditto.things.model.Thing;
 import org.eclipse.ditto.things.model.ThingDefinition;
 import org.eclipse.ditto.things.model.ThingId;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
 import org.eclipse.ditto.wot.model.ThingDescription;
-
-import org.apache.pekko.actor.ActorSystem;
-import org.apache.pekko.actor.Extension;
+import org.eclipse.ditto.wot.model.ThingModel;
 
 /**
- * Extension for providing WoT (Web of Things) {@link ThingDescription}s for given {@code thingId}s from either a
- * {@link ThingDefinition} or a {@link FeatureDefinition} from which the URL to a WoT
- * {@link org.eclipse.ditto.wot.model.ThingModel} is resolved and the {@link ThingDescription} is provided.
+ * Generator for WoT (Web of Things) {@link ThingDescription} based on a given WoT {@link ThingModel} and context of the
+ * Ditto {@link Thing} to generate the ThingDescription for.
  *
  * @since 2.4.0
  */
-@Immutable
-public interface WotThingDescriptionProvider extends Extension {
+public interface WotThingDescriptionGenerator {
 
     /**
      * Provides a {@link ThingDescription} for the given {@code thingDefinition} and {@code thingId} combination.
@@ -78,44 +76,44 @@ CompletionStage provideFeatureTD(ThingId thingId,
             DittoHeaders dittoHeaders);
 
     /**
-     * Provides a {@link Thing} skeleton for the given {@code thingId} using the passed {@code thingDefinition} to
-     * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via
-     * {@link org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator}.
-     * The implementation should not throw exceptions, but return an empty optional if something went wrong during
-     * fetching or generation of the skeleton.
+     * Generates a ThingDescription for the given {@code thingId}, optionally using the passed {@code thing} to lookup
+     * thing specific placeholders.
+     * Uses the passed in {@code thingModel} and generates TD forms, security definition etc. in order to make it a
+     * valid TD.
      *
-     * @param thingId the ThingId to generate the Thing skeleton for.
-     * @param thingDefinition the ThingDefinition to resolve the ThingModel URL from.
-     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
-     * @return an optional Thing skeleton or empty optional if something went wrong during the skeleton creation.
+     * @param thingId the ThingId to generate the ThingDescription for.
+     * @param thing the optional Thing from which to resolve metadata from.
+     * @param placeholderLookupObject the optional JsonObject to dynamically resolve placeholders from
+     * (e.g. a Thing or Feature).
+     * @param featureId the optional feature name if the TD should be generated for a certain feature of the Thing.
+     * @param thingModel the ThingModel to use as template for generating the TD.
+     * @param thingModelUrl the URL from which the ThingModel was fetched.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeException which might occur during the
+     * generation.
+     * @return the generated ThingDescription for the given {@code thingId} based on the passed in {@code thingModel}.
+     * @throws org.eclipse.ditto.wot.model.WotThingModelInvalidException if the WoT ThingModel did not contain the
+     * mandatory {@code "@type"} being {@code "tm:ThingModel"}
      */
-    CompletionStage> provideThingSkeletonForCreation(ThingId thingId,
-            @Nullable ThingDefinition thingDefinition,
-            DittoHeaders dittoHeaders);
-
-    /**
-     * Provides a {@link Feature} skeleton for the given {@code featureId} using the passed {@code featureDefinition} to
-     * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via
-     * {@link org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator}.
-     * The implementation should not throw exceptions, but return an empty optional if something went wrong during
-     * fetching or generation of the skeleton.
-     *
-     * @param featureId the FeatureId to generate the Feature skeleton for.
-     * @param featureDefinition the FeatureDefinition to resolve the ThingModel URL from.
-     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
-     * @return an optional Feature skeleton or empty optional if something went wrong during the skeleton creation.
-     */
-    CompletionStage> provideFeatureSkeletonForCreation(String featureId,
-            @Nullable FeatureDefinition featureDefinition,
+    CompletionStage generateThingDescription(ThingId thingId,
+            @Nullable Thing thing,
+            @Nullable JsonObject placeholderLookupObject,
+            @Nullable String featureId,
+            ThingModel thingModel,
+            URL thingModelUrl,
             DittoHeaders dittoHeaders);
 
     /**
-     * Get the {@code WotThingDescriptionProvider} for an actor system.
+     * Creates a new instance of WotThingDescriptionGenerator with the given {@code wotConfig}.
      *
-     * @param system the actor system.
-     * @return the {@code WotThingDescriptionProvider} extension.
+     * @param wotConfig the WoTConfig to use for creating the generator.
+     * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other
+     * ThingModels during the generation process.
+     * @param executor the executor to use to run async tasks.
+     * @return the created WotThingDescriptionGenerator.
      */
-    static WotThingDescriptionProvider get(final ActorSystem system) {
-        return DefaultWotThingDescriptionProvider.ExtensionId.INSTANCE.get(system);
+    static WotThingDescriptionGenerator of(final WotConfig wotConfig,
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
+        return new DefaultWotThingDescriptionGenerator(wotConfig, thingModelResolver, executor);
     }
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java
similarity index 87%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java
index 38349e5409..c0aee200dd 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,13 +10,13 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
 
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
 import org.eclipse.ditto.wot.model.ThingModel;
 
 /**
@@ -60,13 +60,16 @@ public interface WotThingModelExtensionResolver {
     CompletionStage resolveThingModelRefs(ThingModel thingModel, DittoHeaders dittoHeaders);
 
     /**
-     * Creates a new instance of WotThingModelExtensionResolver with the given {@code thingModelFetcher}.
+     * Creates a new instance of WotThingModelExtensionResolver with the given {@code thingModelFetcher} and
+     * {@code executor}.
      *
      * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the generation process.
-     * @param executor the executor to use for async tasks.
-     * @return the created WotThingSkeletonGenerator.
+     * @param executor the executor to use to run async tasks.
+     * @return the created WotThingModelExtensionResolver.
+     * @since 3.6.0
      */
-    static WotThingModelExtensionResolver of(final WotThingModelFetcher thingModelFetcher, final Executor executor) {
-        return new DefaultWotThingModelExtensionResolver(thingModelFetcher, executor);
+    static WotThingModelExtensionResolver of(final WotThingModelFetcher thingModelFetcher,
+            final Executor executor) {
+        return new DefaultWotThingModelExtensionResolver(thingModelFetcher,executor);
     }
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java
similarity index 69%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java
index c9c724d508..517bbadcf4 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,18 +10,23 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.generator;
+package org.eclipse.ditto.wot.api.generator;
 
 import java.net.URL;
 import java.util.Optional;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+
+import javax.annotation.Nullable;
 
-import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
 import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.FeatureDefinition;
 import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.things.model.ThingDefinition;
 import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
 import org.eclipse.ditto.wot.model.ThingModel;
 
 /**
@@ -57,6 +62,23 @@ default CompletionStage> generateThingSkeleton(ThingId thingId,
         return generateThingSkeleton(thingId, thingModel, thingModelUrl, false, dittoHeaders);
     }
 
+    /**
+     * Provides a {@link Thing} skeleton for the given {@code thingId} using the passed {@code thingDefinition} to
+     * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via
+     * {@link WotThingSkeletonGenerator}.
+     * The implementation should not throw exceptions, but return an empty optional if something went wrong during
+     * fetching or generation of the skeleton.
+     *
+     * @param thingId the ThingId to generate the Thing skeleton for.
+     * @param thingDefinition the ThingDefinition to resolve the ThingModel URL from.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
+     * @return an optional Thing skeleton or empty optional if something went wrong during the skeleton creation.
+     * @since 3.6.0
+     */
+    CompletionStage> provideThingSkeletonForCreation(ThingId thingId,
+            @Nullable ThingDefinition thingDefinition,
+            DittoHeaders dittoHeaders);
+
     /**
      * Generates a skeleton {@link Thing} for the given {@code thingId}.
      * Uses the passed in {@code thingModel} and generates
@@ -107,6 +129,23 @@ default CompletionStage> generateFeatureSkeleton(String featur
         return generateFeatureSkeleton(featureId, thingModel, thingModelUrl, false, dittoHeaders);
     }
 
+    /**
+     * Provides a {@link Feature} skeleton for the given {@code featureId} using the passed {@code featureDefinition} to
+     * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via
+     * {@link WotThingSkeletonGenerator}.
+     * The implementation should not throw exceptions, but return an empty optional if something went wrong during
+     * fetching or generation of the skeleton.
+     *
+     * @param featureId the FeatureId to generate the Feature skeleton for.
+     * @param featureDefinition the FeatureDefinition to resolve the ThingModel URL from.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
+     * @return an optional Feature skeleton or empty optional if something went wrong during the skeleton creation.
+     * @since 3.6.0
+     */
+    CompletionStage> provideFeatureSkeletonForCreation(String featureId,
+            @Nullable FeatureDefinition featureDefinition,
+            DittoHeaders dittoHeaders);
+
     /**
      * Generates a skeleton {@link Feature} for the given {@code featureId}.
      * Uses the passed in {@code thingModel} and generates
@@ -135,11 +174,15 @@ CompletionStage> generateFeatureSkeleton(String featureId,
     /**
      * Creates a new instance of WotThingSkeletonGenerator with the given {@code wotConfig}.
      *
-     * @param actorSystem the actor system to use.
-     * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the generation process.
+     * @param wotConfig the WoT Config to use for creating the generator.
+     * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other
+     * ThingModels during the generation process.
+     * @param executor the executor to use to run async tasks.
      * @return the created WotThingSkeletonGenerator.
      */
-    static WotThingSkeletonGenerator of(final ActorSystem actorSystem, final WotThingModelFetcher thingModelFetcher) {
-        return new DefaultWotThingSkeletonGenerator(actorSystem, thingModelFetcher);
+    static WotThingSkeletonGenerator of(final WotConfig wotConfig,
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
+        return new DefaultWotThingSkeletonGenerator(wotConfig, thingModelResolver, executor);
     }
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java
similarity index 79%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java
index b37be650e0..bb7b5808ab 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -13,7 +13,7 @@
 
 /**
  * The TM to TD Generator for the WoT (Web of Things) integration.
- * @since 2.4.0
+ * @since 3.6.0
  */
 @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
-package org.eclipse.ditto.wot.integration.generator;
\ No newline at end of file
+package org.eclipse.ditto.wot.api.generator;
\ No newline at end of file
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java
new file mode 100644
index 0000000000..7bb4ba7420
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.provider;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.internal.utils.cache.Cache;
+import org.eclipse.ditto.internal.utils.cache.CacheFactory;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.model.IRI;
+import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
+import org.eclipse.ditto.wot.model.ThingModel;
+import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
+import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
+
+/**
+ * Default implementation of {@link WotThingModelFetcher} which should be not Ditto specific.
+ */
+final class DefaultWotThingModelFetcher implements WotThingModelFetcher {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingModelFetcher.class);
+
+    private static final Duration MAX_FETCH_MODEL_DURATION = Duration.ofSeconds(10);
+
+    private final JsonDownloader jsonDownloader;
+    private final Cache thingModelCache;
+
+    DefaultWotThingModelFetcher(final WotConfig wotConfig,
+            final JsonDownloader jsonDownloader,
+            final Executor cacheLoaderExecutor) {
+        this.jsonDownloader = jsonDownloader;
+        final AsyncCacheLoader loader = this::loadThingModelViaHttp;
+        thingModelCache = CacheFactory.createCache(loader,
+                wotConfig.getCacheConfig(),
+                "ditto_wot_thing_model_cache",
+                cacheLoaderExecutor
+        );
+    }
+
+    @Override
+    public CompletableFuture fetchThingModel(final IRI iri, final DittoHeaders dittoHeaders) {
+        try {
+            return fetchThingModel(new URL(iri.toString()), dittoHeaders);
+        } catch (final MalformedURLException e) {
+            throw ThingDefinitionInvalidException.newBuilder(iri)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    @Override
+    public CompletableFuture fetchThingModel(final URL url, final DittoHeaders dittoHeaders) {
+        LOGGER.debug("Fetching ThingModel (from cache or downloading as fallback) from URL: <{}>", url);
+        return thingModelCache.get(url)
+                .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders))
+                .orTimeout(MAX_FETCH_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS);
+    }
+
+    private ThingModel resolveThingModel(@Nullable final ThingModel thingModel,
+            final URL tmUrl,
+            final DittoHeaders dittoHeaders) {
+        if (null != thingModel) {
+            LOGGER.debug("Resolved ThingModel: <{}>", thingModel);
+            return thingModel;
+        } else {
+            throw WotThingModelNotAccessibleException.newBuilder(tmUrl)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    /* this method is used to asynchronously load the ThingModel into the cache */
+    private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) {
+        LOGGER.debug("Loading ThingModel from URL <{}>.", url);
+        final CompletionStage responseFuture = jsonDownloader.downloadJsonViaHttp(url, executor);
+        final CompletionStage thingModelFuture = responseFuture
+                .thenApply(ThingModel::fromJson)
+                .exceptionally(t -> {
+                    LOGGER.warn("Failed to extract ThingModel from response because of <{}: {}>",
+                            t.getClass().getSimpleName(), t.getMessage());
+                    throw WotThingModelInvalidException.newBuilder(url)
+                            .cause(t)
+                            .build();
+                });
+        return thingModelFuture.toCompletableFuture();
+    }
+
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java
new file mode 100644
index 0000000000..0d94f486e0
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.provider;
+
+import java.net.URL;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+
+import org.eclipse.ditto.json.JsonObject;
+
+/**
+ * Provides functionality to asynchronously download a {@link JsonObject} from a given {@link URL}.
+ *
+ * @since 3.6.0
+ */
+public interface JsonDownloader {
+
+    /**
+     * Downloads from the given {@code url} the content as {@code JsonObject} and provides it asynchronously using the
+     * passed {@code executor}.
+     *
+     * @param url the URL to download the json object from.
+     * @param executor the executor to use.
+     * @return a CompletionStage of the downloaded {@code JsonObject}, which may also be failed exceptionally, e.g.
+     * if the resource could not be accessed or of the provided resource could not be parsed as a {@link JsonObject}.
+     */
+    CompletionStage downloadJsonViaHttp(URL url, Executor executor);
+}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java
similarity index 73%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java
index bc9608a3c9..5c5023fcaf 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,14 +10,14 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.provider;
+package org.eclipse.ditto.wot.api.provider;
 
 import java.net.URL;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
 
-import org.apache.pekko.actor.ActorSystem;
 import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
+import org.eclipse.ditto.wot.api.config.WotConfig;
 import org.eclipse.ditto.wot.model.IRI;
 import org.eclipse.ditto.wot.model.ThingModel;
 
@@ -45,7 +45,7 @@ public interface WotThingModelFetcher {
     CompletionStage fetchThingModel(IRI iri, DittoHeaders dittoHeaders);
 
     /**
-     * Fetches the ThingModel resource at the passed {@code iurlri}.
+     * Fetches the ThingModel resource at the passed {@code url}.
      *
      * @param url the URL from which to fetch the ThingModel.
      * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
@@ -58,13 +58,17 @@ public interface WotThingModelFetcher {
     CompletionStage fetchThingModel(URL url, DittoHeaders dittoHeaders);
 
     /**
-     * Creates a new instance of WotThingModelExtensionResolver with the given {@code actorSystem}.
+     * Creates a new instance of WotThingModelFetcher with the given {@code actorSystem} and {@code wotConfig}.
      *
-     * @param actorSystem the actor system to use.
      * @param wotConfig the WoTConfig to use for creating the generator.
-     * @return the created WotThingSkeletonGenerator.
+     * @param jsonDownloader the downloader to use to download a JsonObject from a given URL.
+     * @param cacheLoaderExecutor the executor to use to run async cache loading tasks.
+     * @return the created WotThingModelFetcher.
+     * @since 3.6.0
      */
-    static WotThingModelFetcher of(final ActorSystem actorSystem, final WotConfig wotConfig) {
-        return new DefaultWotThingModelFetcher(actorSystem, wotConfig);
+    static WotThingModelFetcher of(final WotConfig wotConfig,
+            final JsonDownloader jsonDownloader,
+            final Executor cacheLoaderExecutor) {
+        return new DefaultWotThingModelFetcher(wotConfig, jsonDownloader, cacheLoaderExecutor);
     }
 }
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java
similarity index 80%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java
rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java
index 0ab8bd7d38..e479d99dc3 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -13,7 +13,7 @@
 
 /**
  * The provider for ThingDescriptions the WoT in the (Web of Things) integration.
- * @since 2.4.0
+ * @since 3.6.0
  */
 @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
-package org.eclipse.ditto.wot.integration.provider;
\ No newline at end of file
+package org.eclipse.ditto.wot.api.provider;
\ No newline at end of file
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java
new file mode 100644
index 0000000000..fcca26ce59
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.resolver;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.time.Duration;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.internal.utils.cache.Cache;
+import org.eclipse.ditto.internal.utils.cache.CacheFactory;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.model.IRI;
+import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
+import org.eclipse.ditto.wot.model.ThingModel;
+import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
+import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
+
+/**
+ * Default implementation of {@link WotThingModelResolver} which should be not Ditto specific.
+ */
+final class DefaultWotThingModelResolver implements WotThingModelResolver {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingModelResolver.class);
+
+    private static final String TM_SUBMODEL = "tm:submodel";
+    private static final String TM_SUBMODEL_INSTANCE_NAME = "instanceName";
+
+    private static final Duration MAX_RESOLVE_MODEL_DURATION = Duration.ofSeconds(12);
+
+    private final WotThingModelFetcher thingModelFetcher;
+    private final WotThingModelExtensionResolver thingModelExtensionResolver;
+    private final Cache fullyResolvedThingModelCache;
+
+    DefaultWotThingModelResolver(final WotConfig wotConfig,
+            final WotThingModelFetcher thingModelFetcher,
+            final WotThingModelExtensionResolver thingModelExtensionResolver,
+            final Executor cacheLoaderExecutor) {
+        this.thingModelFetcher = thingModelFetcher;
+        this.thingModelExtensionResolver = thingModelExtensionResolver;
+        final AsyncCacheLoader loader = this::loadThingModelViaHttp;
+        fullyResolvedThingModelCache = CacheFactory.createCache(loader,
+                wotConfig.getCacheConfig(),
+                "ditto_wot_fully_resolved_thing_model_cache",
+                cacheLoaderExecutor);
+    }
+
+    @Override
+    public CompletableFuture resolveThingModel(final IRI iri, final DittoHeaders dittoHeaders) {
+        try {
+            return resolveThingModel(new URL(iri.toString()), dittoHeaders);
+        } catch (final MalformedURLException e) {
+            throw ThingDefinitionInvalidException.newBuilder(iri)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    @Override
+    public CompletableFuture resolveThingModel(final URL url, final DittoHeaders dittoHeaders) {
+        LOGGER.debug("Resolving ThingModel (from cache or downloading as fallback) from URL: <{}>", url);
+        return fullyResolvedThingModelCache.get(url)
+                .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders))
+                .orTimeout(MAX_RESOLVE_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS);
+    }
+
+    @Override
+    public CompletionStage> resolveThingModelSubmodels(final ThingModel thingModel,
+            final DittoHeaders dittoHeaders) {
+
+        final List>> futureList =
+                thingModel.getLinks()
+                        .map(links -> links.stream()
+                                .filter(baseLink -> baseLink.getRel().filter(TM_SUBMODEL::equals).isPresent())
+                                .map(baseLink -> {
+                                            final String instanceName = baseLink.getValue(TM_SUBMODEL_INSTANCE_NAME)
+                                                    .filter(JsonValue::isString)
+                                                    .map(JsonValue::asString)
+                                                    .orElseThrow(() -> WotThingModelInvalidException
+                                                            .newBuilder("The required 'instanceName' field of the " +
+                                                                    "'tm:submodel' link was not provided."
+                                                            ).dittoHeaders(dittoHeaders)
+                                                            .build()
+                                                    );
+                                            LOGGER.debug("Resolved TM submodel with instanceName <{}> and href <{}>",
+                                                    instanceName, baseLink.getHref());
+                                            return new ThingSubmodel(instanceName, baseLink.getHref());
+                                        }
+                                )
+                        )
+                        .orElseGet(Stream::empty)
+                        .map(submodel -> resolveThingModel(submodel.href(), dittoHeaders)
+                                .thenApply(subThingModel ->
+                                        new AbstractMap.SimpleEntry<>(submodel, subThingModel)
+                                )
+                                .toCompletableFuture()
+                        )
+                        .toList();
+
+        return CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
+                .thenApplyAsync(aVoid -> futureList.stream()
+                        .map(CompletableFuture::join) // joining does not block anything here as "allOf" already guaranteed that all futures are ready
+                        .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue))
+                );
+    }
+
+    private ThingModel resolveThingModel(@Nullable final ThingModel thingModel,
+            final URL tmUrl,
+            final DittoHeaders dittoHeaders) {
+        if (null != thingModel) {
+            LOGGER.debug("Fully Resolved ThingModel: <{}>", thingModel);
+            return thingModel;
+        } else {
+            throw WotThingModelNotAccessibleException.newBuilder(tmUrl)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+    }
+
+    /* this method is used to asynchronously load the ThingModel into the cache */
+    private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) {
+        LOGGER.debug("Loading ThingModel from URL <{}>.", url);
+        final DittoHeaders dittoHeaders = DittoHeaders.empty();
+        return thingModelFetcher.fetchThingModel(url, dittoHeaders)
+                .thenComposeAsync(thingModel ->
+                                thingModelExtensionResolver
+                                        .resolveThingModelExtensions(thingModel, dittoHeaders)
+                                        .thenCompose(thingModelWithExtensions ->
+                                                thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions,
+                                                        dittoHeaders)
+                                        ),
+                        executor
+                )
+                .toCompletableFuture();
+    }
+
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/ThingSubmodel.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/ThingSubmodel.java
new file mode 100644
index 0000000000..477d9d20d3
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/ThingSubmodel.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.resolver;
+
+import org.eclipse.ditto.wot.model.IRI;
+
+/**
+ * Bundles the {@code instanceName} and the {@code href} of a {@code tm:submodel} contained in the links of a
+ * ThingModel.
+ *
+ * @param instanceName the instance name of the submodel, translates to the "feature ID" in Ditto
+ * @param href the link where the submodel's TM is defined
+ */
+public record ThingSubmodel(String instanceName, IRI href) {
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java
new file mode 100644
index 0000000000..ca7209c463
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.resolver;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.model.IRI;
+import org.eclipse.ditto.wot.model.ThingModel;
+
+/**
+ * Fetches WoT (Web of Things) ThingModels from {@code IRI}s/{@code URL}s, resolves extensions and references and
+ * caches the fully resolved ThingModel in order to not always resolve extensions and references.
+ *
+ * @since 3.6.0
+ */
+public interface WotThingModelResolver {
+
+    /**
+     * Fetches the ThingModel resource at the passed {@code iri} and resolves extensions and references, returning the
+     * fully resolved model.
+     *
+     * @param iri the IRI (URL) from which to fetch the ThingModel.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
+     * @return a CompletionStage containing the fetched ThingModel or completed exceptionally with a
+     * {@link org.eclipse.ditto.wot.model.WotThingModelInvalidException} if the fetched ThingModel could not be
+     * parsed/interpreted as correct WoT ThingModel.
+     * @throws org.eclipse.ditto.wot.model.ThingDefinitionInvalidException if the passed {@code iri} did not contain a
+     * valid URL.
+     * @throws org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException if the ThingModel could not be
+     * fetched at the given {@code iri}.
+     */
+    CompletionStage resolveThingModel(IRI iri, DittoHeaders dittoHeaders);
+
+    /**
+     * Fetches the ThingModel resource at the passed {@code url} and resolves extensions and references, returning the
+     * fully resolved model.
+     *
+     * @param url the URL from which to fetch the ThingModel.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
+     * @return a CompletionStage containing the fetched ThingModel or completed exceptionally with a
+     * {@link org.eclipse.ditto.wot.model.WotThingModelInvalidException} if the fetched ThingModel could not be
+     * parsed/interpreted as correct WoT ThingModel.
+     * @throws org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException if the ThingModel could not be
+     * fetched at the given {@code url}.
+     */
+    CompletionStage resolveThingModel(URL url, DittoHeaders dittoHeaders);
+
+    /**
+     * Fetches all submodels contained in the passed {@code thingModel}, including extensions and references, returning
+     * a Map of all submodels.
+     *
+     * @param thingModel the ThingModel to fetch submodels for.
+     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions.
+     * @return a CompletionStage containing the fetched ThingModel submodels or completed exceptionally with a
+     * {@link org.eclipse.ditto.wot.model.WotThingModelInvalidException} if the fetched ThingModels could not be
+     * parsed/interpreted as correct WoT ThingModels.
+     * @throws org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException if one of the ThingModel submodels
+     * could not be fetched at its defined {@code url}.
+     */
+    CompletionStage> resolveThingModelSubmodels(ThingModel thingModel,
+            DittoHeaders dittoHeaders);
+
+    /**
+     * Creates a new instance of WotThingModelResolver with the given {@code actorSystem} and {@code wotConfig}.
+     *
+     * @param wotConfig the WoT Config to use for creating the resolver.
+     * @param thingModelFetcher the WoT ThingModel fetcher used to download/fetch TMs from URLs.
+     * @param thingModelExtensionResolver the WoT ThingModel extension and reference resolver used to resolve
+     * {@code tm:extends} and {@code tm:ref} constructs in ThingModels.
+     * @param cacheLoaderExecutor the executor to use to run async cache loading tasks.
+     * @return the created WotThingModelResolver.
+     */
+    static WotThingModelResolver of(final WotConfig wotConfig,
+            final WotThingModelFetcher thingModelFetcher,
+            final WotThingModelExtensionResolver thingModelExtensionResolver,
+            final Executor cacheLoaderExecutor) {
+        return new DefaultWotThingModelResolver(wotConfig, thingModelFetcher, thingModelExtensionResolver,
+                cacheLoaderExecutor);
+    }
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java
new file mode 100644
index 0000000000..8679aa4d8a
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+/**
+ * The resolve for loading and resolving (extensions and references in) ThingModels the WoT in the (Web of Things)
+ * integration.
+ * @since 3.6.0
+ */
+@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
+package org.eclipse.ditto.wot.api.resolver;
\ No newline at end of file
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java
new file mode 100644
index 0000000000..4e50d6c554
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.validator;
+
+import java.net.URL;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.base.model.signals.FeatureToggle;
+import org.eclipse.ditto.things.model.DefinitionIdentifier;
+import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.FeatureDefinition;
+import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
+import org.eclipse.ditto.wot.model.ThingModel;
+import org.eclipse.ditto.wot.model.ThingSkeleton;
+import org.eclipse.ditto.wot.validation.WotThingModelValidation;
+
+/**
+ * Default Ditto specific implementation of {@link WotThingModelValidator}.
+ */
+@Immutable
+final class DefaultWotThingModelValidator implements WotThingModelValidator {
+
+    private final WotConfig wotConfig;
+    private final WotThingModelResolver thingModelResolver;
+    private final WotThingModelValidation thingModelValidation;
+    private final Executor executor;
+
+    DefaultWotThingModelValidator(final WotConfig wotConfig,
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
+        this.wotConfig = wotConfig;
+        this.thingModelResolver = thingModelResolver;
+        thingModelValidation = WotThingModelValidation.createInstance(wotConfig.getValidationConfig());
+        this.executor = executor;
+    }
+
+    @Override
+    public CompletionStage validateThing(final Thing thing, final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) {
+            final Optional urlOpt = thing.getDefinition().flatMap(DefinitionIdentifier::getUrl);
+            if (urlOpt.isPresent()) {
+                final URL url = urlOpt.get();
+                final Function> validationFunction =
+                        thingModelWithExtensionsAndImports ->
+                                validateThing(thingModelWithExtensionsAndImports, thing, dittoHeaders);
+                return fetchResolveAndValidateWith(url, dittoHeaders, validationFunction);
+            } else {
+                return CompletableFuture.completedStage(null);
+            }
+        } else {
+            return CompletableFuture.completedStage(null);
+        }
+    }
+
+    @Override
+    public CompletionStage validateThing(final ThingSkeleton thingSkeleton,
+            final Thing thing,
+            final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) {
+            return thingModelValidation.validateThing(thingSkeleton, thing, dittoHeaders);
+        } else {
+            return CompletableFuture.completedStage(null);
+        }
+    }
+
+    @Override
+    public CompletionStage validateFeature(final Feature feature, final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) {
+            final Optional definitionIdentifier = feature.getDefinition()
+                    .map(FeatureDefinition::getFirstIdentifier);
+            final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl);
+            if (urlOpt.isPresent()) {
+                final URL url = urlOpt.get();
+                final Function> validationFunction =
+                        thingModelWithExtensionsAndImports ->
+                                validateFeature(thingModelWithExtensionsAndImports, feature, dittoHeaders);
+                return fetchResolveAndValidateWith(url, dittoHeaders, validationFunction);
+            } else {
+                return CompletableFuture.completedStage(null);
+            }
+        } else {
+            return CompletableFuture.completedStage(null);
+        }
+    }
+
+    @Override
+    public CompletionStage validateFeature(final ThingSkeleton thingSkeleton, final Feature feature,
+            final DittoHeaders dittoHeaders) {
+
+        if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) {
+            return thingModelValidation.validateFeature(thingSkeleton, feature, dittoHeaders);
+        } else {
+            return CompletableFuture.completedStage(null);
+        }
+    }
+
+    private CompletionStage fetchResolveAndValidateWith(final URL url,
+            final DittoHeaders dittoHeaders,
+            final Function> validationFunction) {
+
+        return thingModelResolver.resolveThingModel(url, dittoHeaders)
+                .thenComposeAsync(validationFunction, executor);
+    }
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java
new file mode 100644
index 0000000000..3c5bd5b9a0
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.api.validator;
+
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
+import org.eclipse.ditto.wot.model.ThingSkeleton;
+
+/**
+ * TODO TJ doc
+ * @since 3.6.0
+ */
+public interface WotThingModelValidator {
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateThing(Thing thing, DittoHeaders dittoHeaders);
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateThing(ThingSkeleton thingSkeleton, Thing thing, DittoHeaders dittoHeaders);
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateFeature(Feature feature, DittoHeaders dittoHeaders);
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateFeature(ThingSkeleton thingSkeleton, Feature feature, DittoHeaders dittoHeaders);
+
+    /**
+     * Creates a new instance of WotThingModelValidator with the given {@code wotConfig}.
+     *
+     * @param wotConfig the WoT config to use.
+     * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other
+     * ThingModels during the generation process.
+     * @param executor the executor to use to run async tasks.
+     * @return the created WotThingModelValidator.
+     */
+    static WotThingModelValidator of(final WotConfig wotConfig,
+            final WotThingModelResolver thingModelResolver,
+            final Executor executor) {
+        return new DefaultWotThingModelValidator(wotConfig, thingModelResolver, executor);
+    }
+}
diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java
new file mode 100644
index 0000000000..236a0d49dc
--- /dev/null
+++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+/**
+ * @since 3.6.0
+ */
+@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
+package org.eclipse.ditto.wot.api.validator;
\ No newline at end of file
diff --git a/wot/integration/pom.xml b/wot/integration/pom.xml
index d1552409c0..44146afb07 100755
--- a/wot/integration/pom.xml
+++ b/wot/integration/pom.xml
@@ -34,10 +34,18 @@
             org.eclipse.ditto
             ditto-base-model
         
+        
+            org.eclipse.ditto
+            ditto-wot-api
+        
         
             org.eclipse.ditto
             ditto-wot-model
         
+        
+            org.eclipse.ditto
+            ditto-wot-validation
+        
         
             org.eclipse.ditto
             ditto-things-model
@@ -51,12 +59,6 @@
             org.eclipse.ditto
             ditto-internal-utils-http
         
-
     
 
-    
-        
-        
-    
-
 
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java
new file mode 100644
index 0000000000..1230eeac3a
--- /dev/null
+++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.integration;
+
+import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
+
+import java.util.concurrent.Executor;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.apache.pekko.actor.AbstractExtensionId;
+import org.apache.pekko.actor.ActorSystem;
+import org.apache.pekko.actor.ExtendedActorSystem;
+import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
+import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
+import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
+import org.eclipse.ditto.wot.api.config.DefaultWotConfig;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
+import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver;
+import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
+import org.eclipse.ditto.wot.api.validator.WotThingModelValidator;
+
+/**
+ * Default Ditto specific implementation of {@link DittoWotIntegration} and Pekko extension.
+ */
+@Immutable
+final class DefaultDittoWotIntegration implements DittoWotIntegration {
+
+    private static final ThreadSafeDittoLogger LOGGER =
+            DittoLoggerFactory.getThreadSafeLogger(DefaultDittoWotIntegration.class);
+
+    private final WotConfig wotConfig;
+    private final WotThingModelFetcher thingModelFetcher;
+    private final WotThingModelExtensionResolver thingModelExtensionResolver;
+    private final WotThingModelResolver thingModelResolver;
+    private final WotThingDescriptionGenerator thingDescriptionGenerator;
+    private final WotThingSkeletonGenerator thingSkeletonGenerator;
+    private final WotThingModelValidator thingModelValidator;
+
+    private DefaultDittoWotIntegration(final ActorSystem actorSystem, final WotConfig wotConfig) {
+        this.wotConfig = checkNotNull(wotConfig, "wotConfig");
+        LOGGER.info("Initializing DefaultDittoWotIntegration with config: {}", wotConfig);
+
+        final Executor executor = actorSystem.dispatchers().lookup("wot-dispatcher");
+        final Executor cacheLoaderExecutor = actorSystem.dispatchers().lookup("wot-dispatcher-cache-loader");
+        final PekkoHttpJsonDownloader httpThingModelDownloader =
+                new PekkoHttpJsonDownloader(actorSystem, wotConfig);
+        thingModelFetcher = WotThingModelFetcher.of(wotConfig, httpThingModelDownloader, cacheLoaderExecutor);
+        thingModelExtensionResolver = WotThingModelExtensionResolver.of(thingModelFetcher, executor);
+        thingModelResolver =
+                WotThingModelResolver.of(wotConfig, thingModelFetcher, thingModelExtensionResolver, cacheLoaderExecutor);
+        thingDescriptionGenerator = WotThingDescriptionGenerator.of(wotConfig, thingModelResolver, executor);
+        thingSkeletonGenerator = WotThingSkeletonGenerator.of(wotConfig, thingModelResolver, executor);
+        thingModelValidator = WotThingModelValidator.of(wotConfig, thingModelResolver, executor);
+    }
+
+    /**
+     * Returns a new {@code DefaultWotThingDescriptionProvider} for the given parameters.
+     *
+     * @param actorSystem the actor system to use.
+     * @param wotConfig the WoT config to use.
+     * @return the DefaultWotThingDescriptionProvider.
+     * @throws NullPointerException if any argument is {@code null}.
+     */
+    public static DefaultDittoWotIntegration of(final ActorSystem actorSystem, final WotConfig wotConfig) {
+        return new DefaultDittoWotIntegration(actorSystem, wotConfig);
+    }
+
+    @Override
+    public WotConfig getWotConfig() {
+        return wotConfig;
+    }
+
+    @Override
+    public WotThingModelFetcher getWotThingModelFetcher() {
+        return thingModelFetcher;
+    }
+
+    @Override
+    public WotThingModelResolver getWotThingModelResolver() {
+        return thingModelResolver;
+    }
+
+    @Override
+    public WotThingDescriptionGenerator getWotThingDescriptionGenerator() {
+        return thingDescriptionGenerator;
+    }
+
+    @Override
+    public WotThingModelExtensionResolver getWotThingModelExtensionResolver() {
+        return thingModelExtensionResolver;
+    }
+
+    @Override
+    public WotThingSkeletonGenerator getWotThingSkeletonGenerator() {
+        return thingSkeletonGenerator;
+    }
+
+    @Override
+    public WotThingModelValidator getWotThingModelValidator() {
+        return thingModelValidator;
+    }
+
+
+    static final class ExtensionId extends AbstractExtensionId {
+
+        static final ExtensionId INSTANCE = new ExtensionId();
+
+        private ExtensionId() {}
+
+        @Override
+        public DittoWotIntegration createExtension(final ExtendedActorSystem system) {
+            final WotConfig wotConfig = DefaultWotConfig.of(
+                    DefaultScopedConfig.dittoScoped(system.settings().config())
+                            .getConfig(DefaultWotConfig.WOT_PARENT_CONFIG_PATH)
+            );
+            return of(system, wotConfig);
+        }
+    }
+
+}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java
new file mode 100644
index 0000000000..dc912b9214
--- /dev/null
+++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.integration;
+
+import org.apache.pekko.actor.ActorSystem;
+import org.apache.pekko.actor.Extension;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator;
+import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver;
+import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator;
+import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher;
+import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver;
+import org.eclipse.ditto.wot.api.validator.WotThingModelValidator;
+
+/**
+ * Extension providing access to all Ditto WoT integration capabilities.
+ *
+ * @since 3.6.0
+ */
+public interface DittoWotIntegration extends Extension {
+
+    /**
+     * @return the applied WoT configuration.
+     */
+    WotConfig getWotConfig();
+
+    /**
+     * @return the WoT ThingModel fetcher used to download/fetch TMs from URLs.
+     */
+    WotThingModelFetcher getWotThingModelFetcher();
+
+    /**
+     * @return the WoT ThingModel resolver which fetches ThingModels and in addition resolves extensions
+     * and reference to other ThingModels.
+     */
+    WotThingModelResolver getWotThingModelResolver();
+
+    /**
+     * @return the WoT ThingDescription generator which generates ThingDescriptions based on ThingModels.
+     */
+    WotThingDescriptionGenerator getWotThingDescriptionGenerator();
+
+    /**
+     * @return the WoT ThingModel extension and reference resolver used to resolve {@code tm:extends} and
+     * {@code tm:ref} constructs in ThingModels.
+     */
+    WotThingModelExtensionResolver getWotThingModelExtensionResolver();
+
+    /**
+     * @return the WoT Thing skeleton generator which generates a JSON skeleton when creating new things
+     * or features based on a ThingModel, adhering to default values of the model.
+     */
+    WotThingSkeletonGenerator getWotThingSkeletonGenerator();
+
+    /**
+     * @return the WoT ThingModel validator which can validate and enforce Ditto Thing payloads based on the
+     * defined ThingModel and its JsonSchema for properties, actions, events.
+     */
+    WotThingModelValidator getWotThingModelValidator();
+
+    /**
+     * Get the {@code DittoWotIntegration} for an actor system.
+     *
+     * @param system the actor system.
+     * @return the {@code DittoWotIntegration} extension.
+     */
+    static DittoWotIntegration get(final ActorSystem system) {
+        return DefaultDittoWotIntegration.ExtensionId.INSTANCE.get(system);
+    }
+}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java
similarity index 56%
rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java
rename to wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java
index 765897006a..342d184ce9 100644
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java
+++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -10,39 +10,15 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-package org.eclipse.ditto.wot.integration.provider;
+package org.eclipse.ditto.wot.integration;
 
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.MessageFormat;
-import java.time.Duration;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-
-import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
-import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
-import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
-import org.eclipse.ditto.internal.utils.cache.Cache;
-import org.eclipse.ditto.internal.utils.cache.CacheFactory;
-import org.eclipse.ditto.internal.utils.http.DefaultHttpClientFacade;
-import org.eclipse.ditto.internal.utils.http.HttpClientFacade;
-import org.eclipse.ditto.json.JsonFactory;
-import org.eclipse.ditto.json.JsonObject;
-import org.eclipse.ditto.json.JsonValue;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
-import org.eclipse.ditto.wot.model.IRI;
-import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
-import org.eclipse.ditto.wot.model.ThingModel;
-import org.eclipse.ditto.wot.model.WotThingModelInvalidException;
-import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException;
-
-import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
 
 import org.apache.pekko.actor.ActorSystem;
 import org.apache.pekko.http.javadsl.model.HttpHeader;
@@ -56,16 +32,25 @@
 import org.apache.pekko.stream.SystemMaterializer;
 import org.apache.pekko.stream.javadsl.Sink;
 import org.apache.pekko.util.ByteString;
+import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
+import org.eclipse.ditto.internal.utils.http.DefaultHttpClientFacade;
+import org.eclipse.ditto.internal.utils.http.HttpClientFacade;
+import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
+import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
+import org.eclipse.ditto.json.JsonFactory;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.wot.api.config.WotConfig;
+import org.eclipse.ditto.wot.api.provider.JsonDownloader;
+import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException;
 
 /**
- * Default implementation of {@link WotThingModelFetcher} which should be not Ditto specific.
+ * Pekko HTTP based implementation of {@link JsonDownloader}.
  */
-final class DefaultWotThingModelFetcher implements WotThingModelFetcher {
+final class PekkoHttpJsonDownloader implements JsonDownloader {
 
     private static final ThreadSafeDittoLogger LOGGER =
-            DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingModelFetcher.class);
-
-    private static final Duration MAX_FETCH_MODEL_DURATION = Duration.ofSeconds(10);
+            DittoLoggerFactory.getThreadSafeLogger(PekkoHttpJsonDownloader.class);
 
     private static final HttpHeader ACCEPT_HEADER = Accept.create(
             MediaRanges.create(MediaTypes.applicationWithOpenCharset("tm+json")),
@@ -74,61 +59,21 @@ final class DefaultWotThingModelFetcher implements WotThingModelFetcher {
 
     private final HttpClientFacade httpClient;
     private final Materializer materializer;
-    private final Cache thingModelCache;
 
-    DefaultWotThingModelFetcher(final ActorSystem actorSystem, final WotConfig wotConfig) {
+    PekkoHttpJsonDownloader(final ActorSystem actorSystem, final WotConfig wotConfig) {
         this.httpClient = DefaultHttpClientFacade.getInstance(actorSystem, wotConfig.getHttpProxyConfig());
         materializer = SystemMaterializer.get(actorSystem).materializer();
-        final AsyncCacheLoader loader = this::loadThingModelViaHttp;
-        thingModelCache = CacheFactory.createCache(loader,
-                wotConfig.getCacheConfig(),
-                "ditto_wot_thing_model_cache",
-                actorSystem.dispatchers().lookup("wot-dispatcher-cache-loader"));
-    }
-
-    @Override
-    public CompletableFuture fetchThingModel(final IRI iri, final DittoHeaders dittoHeaders) {
-        try {
-            return fetchThingModel(new URL(iri.toString()), dittoHeaders);
-        } catch (final MalformedURLException e) {
-            throw ThingDefinitionInvalidException.newBuilder(iri)
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
     }
 
     @Override
-    public CompletableFuture fetchThingModel(final URL url, final DittoHeaders dittoHeaders) {
-        LOGGER.withCorrelationId(dittoHeaders)
-                .debug("Fetching ThingModel (from cache or downloading as fallback) from URL: <{}>", url);
-        return thingModelCache.get(url)
-                .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders))
-                .orTimeout(MAX_FETCH_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS);
-    }
-
-    private ThingModel resolveThingModel(@Nullable final ThingModel thingModel,
-            final URL tmUrl,
-            final DittoHeaders dittoHeaders) {
-        if (null != thingModel) {
-            LOGGER.withCorrelationId(dittoHeaders).debug("Resolved ThingModel: <{}>", thingModel);
-            return thingModel;
-        } else {
-            throw WotThingModelNotAccessibleException.newBuilder(tmUrl)
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
-    }
-
-    /* this method is used to asynchronously load the ThingModel into the cache */
-    private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) {
-        LOGGER.debug("Loading ThingModel from URL <{}>.", url);
-        final CompletionStage responseFuture = getThingModelFromUrl(url);
-        final CompletionStage thingModelFuture = responseFuture.thenCompose(
-                response -> mapResponseToThingModel(response, url));
+    public CompletionStage downloadJsonViaHttp(final URL url, final Executor executor) {
+        LOGGER.debug("Loading JsonObject from URL <{}>.", url);
+        final CompletionStage responseFuture = getJsonObjectFromUrl(url);
+        final CompletionStage thingModelFuture = responseFuture.thenCompose(this::mapResponseToJsonObject);
         return thingModelFuture.toCompletableFuture();
     }
 
-    private CompletionStage getThingModelFromUrl(final URL url) {
+    private CompletionStage getJsonObjectFromUrl(final URL url) {
         return httpClient.createSingleHttpRequest(HttpRequest.GET(url.toString()).withHeaders(List.of(ACCEPT_HEADER)))
                 .thenCompose(response -> {
                     if (response.status().isRedirection()) {
@@ -142,7 +87,7 @@ private CompletionStage getThingModelFromUrl(final URL url) {
                                                 cause -> handleUnexpectedException(cause, url));
                                     }
                                 })
-                                .map(this::getThingModelFromUrl) // recurse following the redirect
+                                .map(this::getJsonObjectFromUrl) // recurse following the redirect
                                 .orElseGet(() -> CompletableFuture.completedFuture(response));
                     } else {
                         return CompletableFuture.completedFuture(response);
@@ -160,20 +105,6 @@ private CompletionStage getThingModelFromUrl(final URL url) {
                 });
     }
 
-    private CompletableFuture mapResponseToThingModel(final HttpResponse response, final URL url) {
-        final CompletableFuture bodyFuture = mapResponseToJsonObject(response)
-                .toCompletableFuture();
-        return bodyFuture
-                .thenApply(ThingModel::fromJson)
-                .exceptionally(t -> {
-                    LOGGER.warn("Failed to extract ThingModel from response <{}> because of <{}: {}>", response,
-                            t.getClass().getSimpleName(), t.getMessage());
-                    throw WotThingModelInvalidException.newBuilder(url)
-                            .cause(t)
-                            .build();
-                });
-    }
-
     private CompletionStage mapResponseToJsonObject(final HttpResponse response) {
         return response.entity().getDataBytes().fold(ByteString.emptyByteString(), ByteString::concat)
                 .map(ByteString::utf8String)
@@ -184,7 +115,7 @@ private CompletionStage mapResponseToJsonObject(final HttpResponse r
 
     private void handleNonSuccessResponse(final HttpResponse response, final URL url) {
         final String msg = MessageFormat.format(
-                "Got non success response from ThingModel endpoint with status code: <{0}>", response.status());
+                "Got non success response from JsonObject endpoint with status code: <{0}>", response.status());
         getBodyAsString(response)
                 .thenAccept(stringBody -> LOGGER.info("{} and body: <{}>.", msg, stringBody));
         throw WotThingModelNotAccessibleException.newBuilder(url)
@@ -199,7 +130,7 @@ private CompletionStage getBodyAsString(final HttpResponse response) {
 
 
     private static DittoRuntimeException handleUnexpectedException(final Throwable e, final URL url) {
-        final String msg = MessageFormat.format("Got Exception from ThingModel endpoint <{0}>.", url);
+        final String msg = MessageFormat.format("Got Exception from JsonObject endpoint <{0}>.", url);
         LOGGER.warn(msg, e);
         throw WotThingModelNotAccessibleException.newBuilder(url)
                 .cause(e)
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java
deleted file mode 100644
index b3eb6ad9eb..0000000000
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.ditto.wot.integration.generator;
-
-import java.net.URL;
-import java.util.concurrent.CompletionStage;
-
-import javax.annotation.Nullable;
-
-import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.json.JsonObject;
-import org.eclipse.ditto.things.model.Thing;
-import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
-import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher;
-import org.eclipse.ditto.wot.model.ThingDescription;
-import org.eclipse.ditto.wot.model.ThingModel;
-
-import org.apache.pekko.actor.ActorSystem;
-
-/**
- * Generator for WoT (Web of Things) {@link ThingDescription} based on a given WoT {@link ThingModel} and context of the
- * Ditto {@link Thing} to generate the ThingDescription for.
- *
- * @since 2.4.0
- */
-public interface WotThingDescriptionGenerator {
-
-    /**
-     * Generates a ThingDescription for the given {@code thingId}, optionally using the passed {@code thing} to lookup
-     * thing specific placeholders.
-     * Uses the passed in {@code thingModel} and generates TD forms, security definition etc. in order to make it a
-     * valid TD.
-     *
-     * @param thingId the ThingId to generate the ThingDescription for.
-     * @param thing the optional Thing from which to resolve metadata from.
-     * @param placeholderLookupObject the optional JsonObject to dynamically resolve placeholders from
-     * (e.g. a Thing or Feature).
-     * @param featureId the optional feature name if the TD should be generated for a certain feature of the Thing.
-     * @param thingModel the ThingModel to use as template for generating the TD.
-     * @param thingModelUrl the URL from which the ThingModel was fetched.
-     * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeException which might occur during the
-     * generation.
-     * @return the generated ThingDescription for the given {@code thingId} based on the passed in {@code thingModel}.
-     * @throws org.eclipse.ditto.wot.model.WotThingModelInvalidException if the WoT ThingModel did not contain the
-     * mandatory {@code "@type"} being {@code "tm:ThingModel"}
-     */
-    CompletionStage generateThingDescription(ThingId thingId,
-            @Nullable Thing thing,
-            @Nullable JsonObject placeholderLookupObject,
-            @Nullable String featureId,
-            ThingModel thingModel,
-            URL thingModelUrl,
-            DittoHeaders dittoHeaders);
-
-    /**
-     * Creates a new instance of WotThingDescriptionGenerator with the given {@code wotConfig}.
-     *
-     * @param actorSystem the actor system to use.
-     * @param wotConfig the WoTConfig to use for creating the generator.
-     * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the TD generation
-     * process.
-     * @return the created WotThingDescriptionGenerator.
-     */
-    static WotThingDescriptionGenerator of(final ActorSystem actorSystem,
-            final WotConfig wotConfig,
-            final WotThingModelFetcher thingModelFetcher) {
-        return new DefaultWotThingDescriptionGenerator(actorSystem, wotConfig, thingModelFetcher);
-    }
-}
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java
new file mode 100644
index 0000000000..c7e6892b61
--- /dev/null
+++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+/**
+ * The Ditto WoT integration using the "Ditto WoT API" with Akka specifics, e.g. in order to fetch WoT models via HTTP.
+ * @since 3.6.0
+ */
+@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
+package org.eclipse.ditto.wot.integration;
\ No newline at end of file
diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java
deleted file mode 100644
index 4e51f11e45..0000000000
--- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.ditto.wot.integration.provider;
-
-import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
-
-import java.net.URL;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Executor;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import org.apache.pekko.actor.AbstractExtensionId;
-import org.apache.pekko.actor.ActorSystem;
-import org.apache.pekko.actor.ExtendedActorSystem;
-import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
-import org.eclipse.ditto.base.model.headers.DittoHeaders;
-import org.eclipse.ditto.base.model.signals.FeatureToggle;
-import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
-import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
-import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
-import org.eclipse.ditto.json.JsonValue;
-import org.eclipse.ditto.things.model.DefinitionIdentifier;
-import org.eclipse.ditto.things.model.Feature;
-import org.eclipse.ditto.things.model.FeatureDefinition;
-import org.eclipse.ditto.things.model.Thing;
-import org.eclipse.ditto.things.model.ThingDefinition;
-import org.eclipse.ditto.things.model.ThingId;
-import org.eclipse.ditto.wot.integration.config.DefaultWotConfig;
-import org.eclipse.ditto.wot.integration.config.WotConfig;
-import org.eclipse.ditto.wot.integration.generator.WotThingDescriptionGenerator;
-import org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator;
-import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException;
-import org.eclipse.ditto.wot.model.ThingDescription;
-import org.eclipse.ditto.wot.model.WotInternalErrorException;
-
-/**
- * Default Ditto specific implementation of {@link WotThingDescriptionProvider}.
- */
-@Immutable
-final class DefaultWotThingDescriptionProvider implements WotThingDescriptionProvider {
-
-    private static final ThreadSafeDittoLogger LOGGER =
-            DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingDescriptionProvider.class);
-
-    public static final String MODEL_PLACEHOLDERS_KEY = "model-placeholders";
-
-    private final WotConfig wotConfig;
-    private final WotThingModelFetcher thingModelFetcher;
-    private final WotThingDescriptionGenerator thingDescriptionGenerator;
-    private final WotThingSkeletonGenerator thingSkeletonGenerator;
-    private final Executor executor;
-
-    private DefaultWotThingDescriptionProvider(final ActorSystem actorSystem, final WotConfig wotConfig) {
-        this.wotConfig = checkNotNull(wotConfig, "wotConfig");
-        thingModelFetcher = new DefaultWotThingModelFetcher(actorSystem, wotConfig);
-        thingDescriptionGenerator = WotThingDescriptionGenerator.of(actorSystem, wotConfig, thingModelFetcher);
-        thingSkeletonGenerator = WotThingSkeletonGenerator.of(actorSystem, thingModelFetcher);
-        executor = actorSystem.dispatchers().lookup("wot-dispatcher");
-    }
-
-    /**
-     * Returns a new {@code DefaultWotThingDescriptionProvider} for the given parameters.
-     *
-     * @param actorSystem the actor system to use.
-     * @param wotConfig the WoT config to use.
-     * @return the DefaultWotThingDescriptionProvider.
-     * @throws NullPointerException if any argument is {@code null}.
-     */
-    public static DefaultWotThingDescriptionProvider of(final ActorSystem actorSystem, final WotConfig wotConfig) {
-        return new DefaultWotThingDescriptionProvider(actorSystem, wotConfig);
-    }
-
-    @Override
-    public CompletionStage provideThingTD(@Nullable final ThingDefinition thingDefinition,
-            final ThingId thingId,
-            @Nullable final Thing thing,
-            final DittoHeaders dittoHeaders) {
-        if (null != thingDefinition) {
-            return getWotThingDescriptionForThing(thingDefinition, thingId, thing, dittoHeaders);
-        } else {
-            throw ThingDefinitionInvalidException.newBuilder(null)
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
-    }
-
-    @Override
-    public CompletionStage provideFeatureTD(final ThingId thingId,
-            @Nullable final Thing thing,
-            final Feature feature,
-            final DittoHeaders dittoHeaders) {
-
-        checkNotNull(feature, "feature");
-        if (feature.getDefinition().isPresent()) {
-            return getWotThingDescriptionForFeature(thingId, thing, feature, dittoHeaders);
-        } else {
-            throw ThingDefinitionInvalidException.newBuilder(null)
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
-    }
-
-    @Override
-    public CompletionStage> provideThingSkeletonForCreation(final ThingId thingId,
-            @Nullable final ThingDefinition thingDefinition,
-            final DittoHeaders dittoHeaders) {
-
-        final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders);
-        if (FeatureToggle.isWotIntegrationFeatureEnabled() &&
-                wotConfig.getCreationConfig().getThingCreationConfig().isSkeletonCreationEnabled() &&
-                null != thingDefinition) {
-            final Optional urlOpt = thingDefinition.getUrl();
-            if (urlOpt.isPresent()) {
-                final URL url = urlOpt.get();
-                logger.debug("Fetching ThingModel from <{}> in order to create Thing skeleton for new Thing " +
-                        "with id <{}>", url, thingId);
-                return thingModelFetcher.fetchThingModel(url, dittoHeaders)
-                        .thenComposeAsync(thingModel -> thingSkeletonGenerator.generateThingSkeleton(
-                                        thingId,
-                                        thingModel,
-                                        url,
-                                        wotConfig.getCreationConfig()
-                                                .getThingCreationConfig()
-                                                .shouldGenerateDefaultsForOptionalProperties(),
-                                        dittoHeaders
-                                ),
-                                executor
-                        )
-                        .handle((thingSkeleton, throwable) -> {
-                            if (throwable != null) {
-                                logger.info("Could not fetch ThingModel or generate Thing skeleton based on it due " +
-                                                "to: <{}: {}>",
-                                        throwable.getClass().getSimpleName(), throwable.getMessage(), throwable);
-                                if (wotConfig.getCreationConfig()
-                                        .getThingCreationConfig()
-                                        .shouldThrowExceptionOnWotErrors()) {
-                                    throw DittoRuntimeException.asDittoRuntimeException(
-                                            throwable, t -> WotInternalErrorException.newBuilder()
-                                                    .dittoHeaders(dittoHeaders)
-                                                    .cause(t)
-                                                    .build()
-                                    );
-                                } else {
-                                    return Optional.empty();
-                                }
-                            } else {
-                                logger.debug("Created Thing skeleton for new Thing with id <{}>: <{}>", thingId,
-                                        thingSkeleton);
-                                return thingSkeleton;
-                            }
-                        });
-            } else {
-                return CompletableFuture.completedFuture(Optional.empty());
-            }
-        } else {
-            return CompletableFuture.completedFuture(Optional.empty());
-        }
-    }
-
-    @Override
-    public CompletionStage> provideFeatureSkeletonForCreation(final String featureId,
-            @Nullable final FeatureDefinition featureDefinition, final DittoHeaders dittoHeaders) {
-
-        final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders);
-        if (FeatureToggle.isWotIntegrationFeatureEnabled() &&
-                wotConfig.getCreationConfig().getFeatureCreationConfig().isSkeletonCreationEnabled() &&
-                null != featureDefinition) {
-            final Optional urlOpt = featureDefinition.getFirstIdentifier().getUrl();
-            if (urlOpt.isPresent()) {
-                final URL url = urlOpt.get();
-                logger.debug("Fetching ThingModel from <{}> in order to create Feature skeleton for new Feature " +
-                        "with id <{}>", url, featureId);
-                return thingModelFetcher.fetchThingModel(url, dittoHeaders)
-                        .thenComposeAsync(thingModel -> thingSkeletonGenerator.generateFeatureSkeleton(
-                                        featureId,
-                                        thingModel,
-                                        url,
-                                        wotConfig.getCreationConfig()
-                                                .getFeatureCreationConfig()
-                                                .shouldGenerateDefaultsForOptionalProperties(),
-                                        dittoHeaders
-                                ),
-                                executor
-                        )
-                        .handle((featureSkeleton, throwable) -> {
-                            if (throwable != null) {
-                                logger.info("Could not fetch ThingModel or generate Feature skeleton based on it due " +
-                                                "to: <{}: {}>",
-                                        throwable.getClass().getSimpleName(), throwable.getMessage(), throwable);
-                                if (wotConfig.getCreationConfig()
-                                        .getFeatureCreationConfig()
-                                        .shouldThrowExceptionOnWotErrors()) {
-                                    throw DittoRuntimeException.asDittoRuntimeException(
-                                            throwable, t -> WotInternalErrorException.newBuilder()
-                                                    .dittoHeaders(dittoHeaders)
-                                                    .cause(t)
-                                                    .build()
-                                    );
-                                } else {
-                                    return Optional.empty();
-                                }
-                            } else {
-                                logger.debug("Created Feature skeleton for new Feature with id <{}>: <{}>", featureId,
-                                        featureSkeleton);
-                                return featureSkeleton;
-                            }
-                        });
-            } else {
-                return CompletableFuture.completedFuture(Optional.empty());
-            }
-        } else {
-            return CompletableFuture.completedFuture(Optional.empty());
-        }
-    }
-
-    /**
-     * Download TM, add it to local cache + build TD + return it
-     */
-    private CompletionStage getWotThingDescriptionForThing(final ThingDefinition definitionIdentifier,
-            final ThingId thingId,
-            @Nullable final Thing thing,
-            final DittoHeaders dittoHeaders) {
-
-        final Optional urlOpt = definitionIdentifier.getUrl();
-        if (urlOpt.isPresent()) {
-            final URL url = urlOpt.get();
-            return thingModelFetcher.fetchThingModel(url, dittoHeaders)
-                    .thenComposeAsync(thingModel -> thingDescriptionGenerator
-                                    .generateThingDescription(thingId,
-                                            thing,
-                                            Optional.ofNullable(thing)
-                                                    .flatMap(Thing::getAttributes)
-                                                    .flatMap(a -> a.getValue(MODEL_PLACEHOLDERS_KEY))
-                                                    .filter(JsonValue::isObject)
-                                                    .map(JsonValue::asObject)
-                                                    .orElse(null),
-                                            null,
-                                            thingModel,
-                                            url,
-                                            dittoHeaders
-                                    ),
-                            executor
-                    )
-                    .exceptionally(throwable -> {
-                        throw DittoRuntimeException.asDittoRuntimeException(throwable, t ->
-                                WotInternalErrorException.newBuilder()
-                                        .dittoHeaders(dittoHeaders)
-                                        .cause(t)
-                                        .build());
-                    });
-        } else {
-            throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier)
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
-    }
-
-    /**
-     * Download TM, add it to local cache + build TD + return it
-     */
-    private CompletionStage getWotThingDescriptionForFeature(final ThingId thingId,
-            @Nullable final Thing thing,
-            final Feature feature,
-            final DittoHeaders dittoHeaders) {
-
-        final Optional definitionIdentifier = feature.getDefinition()
-                .map(FeatureDefinition::getFirstIdentifier);
-        final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl);
-        if (urlOpt.isPresent()) {
-            final URL url = urlOpt.get();
-            return thingModelFetcher.fetchThingModel(url, dittoHeaders)
-                    .thenComposeAsync(thingModel -> thingDescriptionGenerator
-                                    .generateThingDescription(thingId,
-                                            thing,
-                                            feature.getProperties()
-                                                    .flatMap(p -> p.getValue(MODEL_PLACEHOLDERS_KEY))
-                                                    .filter(JsonValue::isObject)
-                                                    .map(JsonValue::asObject)
-                                                    .orElse(null),
-                                            feature.getId(),
-                                            thingModel,
-                                            url,
-                                            dittoHeaders
-                                    ),
-                            executor
-                    )
-                    .exceptionally(throwable -> {
-                        throw DittoRuntimeException.asDittoRuntimeException(throwable, t ->
-                                WotInternalErrorException.newBuilder()
-                                        .dittoHeaders(dittoHeaders)
-                                        .cause(t)
-                                        .build());
-                    });
-        } else {
-            throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier.orElse(null))
-                    .dittoHeaders(dittoHeaders)
-                    .build();
-        }
-    }
-
-    static final class ExtensionId extends AbstractExtensionId {
-
-        private static final String WOT_PARENT_CONFIG_PATH = "things";
-
-        static final ExtensionId INSTANCE = new ExtensionId();
-
-        private ExtensionId() {}
-
-        @Override
-        public WotThingDescriptionProvider createExtension(final ExtendedActorSystem system) {
-            final WotConfig wotConfig = DefaultWotConfig.of(
-                    DefaultScopedConfig.dittoScoped(system.settings().config()).getConfig(WOT_PARENT_CONFIG_PATH)
-            );
-            return of(system, wotConfig);
-        }
-    }
-
-}
diff --git a/wot/model/pom.xml b/wot/model/pom.xml
index 9ea16a3627..6c3f4fe0ff 100755
--- a/wot/model/pom.xml
+++ b/wot/model/pom.xml
@@ -91,6 +91,7 @@
                 
                     
                         
+                            org.atteo.classindex,
                             !org.eclipse.ditto.utils.jsr305.annotations,
                             org.eclipse.ditto.*
                         
diff --git a/wot/pom.xml b/wot/pom.xml
index 4847a05617..93d142b8b7 100644
--- a/wot/pom.xml
+++ b/wot/pom.xml
@@ -1,6 +1,6 @@
 
 
+
+    4.0.0
+
+    
+        org.eclipse.ditto
+        ditto-wot
+        ${revision}
+    
+
+    ditto-wot-validation
+    Eclipse Ditto :: WoT :: Validation
+
+    
+    
+
+    
+        
+            org.eclipse.ditto
+            ditto-json
+        
+        
+            org.eclipse.ditto
+            ditto-base-model
+        
+        
+            org.eclipse.ditto
+            ditto-things-model
+        
+
+        
+            org.eclipse.ditto
+            ditto-wot-model
+        
+
+        
+            org.eclipse.ditto
+            ditto-internal-utils-config
+        
+        
+            org.eclipse.ditto
+            ditto-internal-utils-json
+        
+
+        
+            com.networknt
+            json-schema-validator
+        
+
+        
+            ch.qos.logback
+            logback-classic
+            test
+        
+    
+
+
\ No newline at end of file
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java
new file mode 100644
index 0000000000..a18124286b
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation;
+
+import java.util.AbstractMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.json.JsonKey;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.json.JsonPointer;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.things.model.Attributes;
+import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.wot.model.Properties;
+import org.eclipse.ditto.wot.model.ThingModel;
+import org.eclipse.ditto.wot.model.ThingSkeleton;
+import org.eclipse.ditto.wot.model.TmOptionalElement;
+import org.eclipse.ditto.wot.validation.config.TmValidationConfig;
+
+import com.networknt.schema.output.OutputUnit;
+
+final class DefaultWotThingModelValidation implements WotThingModelValidation {
+
+    private static final String ATTRIBUTES = "attributes";
+
+    private final TmValidationConfig validationConfig;
+    private final JsonSchemaTools jsonSchemaTools;
+
+    public DefaultWotThingModelValidation(final TmValidationConfig validationConfig) {
+        this.validationConfig = validationConfig;
+        jsonSchemaTools = new JsonSchemaTools();
+    }
+
+    @Override
+    public CompletionStage validateThing(final ThingSkeleton thingSkeleton,
+            final Thing thing,
+            final DittoHeaders dittoHeaders) {
+
+        if (validationConfig.getThingValidationConfig().isEnforceAttributes()) {
+            return enforceThingAttributes(thingSkeleton, thing, dittoHeaders);
+        }
+        if (validationConfig.getFeatureValidationConfig().isEnforceProperties()) {
+            return enforceThingFeatures(thingSkeleton, thing, dittoHeaders);
+        }
+        return success();
+    }
+
+    private CompletableFuture enforceThingAttributes(final ThingSkeleton thingSkeleton,
+            final Thing thing,
+            final DittoHeaders dittoHeaders) {
+        return thingSkeleton.getProperties()
+                .map(tdProperties -> {
+                    final Attributes attributes =
+                            thing.getAttributes().orElseGet(() -> Attributes.newBuilder().build());
+
+                    final CompletableFuture ensureRequiredPropertiesStage =
+                            ensureRequiredProperties(thingSkeleton, dittoHeaders, tdProperties, attributes,
+                                    ATTRIBUTES, JsonPointer.of(ATTRIBUTES));
+
+                    final CompletableFuture ensureOnlyDefinedPropertiesStage;
+                    if (!validationConfig.getThingValidationConfig().isAllowNonModeledAttributes()) {
+                        ensureOnlyDefinedPropertiesStage =
+                                ensureOnlyDefinedProperties(dittoHeaders, tdProperties, attributes, ATTRIBUTES);
+                    } else {
+                        ensureOnlyDefinedPropertiesStage = CompletableFuture.completedFuture(null);
+                    }
+
+                    final CompletableFuture validatePropertiesStage =
+                            getValidatePropertiesStage(dittoHeaders, tdProperties, attributes,
+                                    ATTRIBUTES, JsonPointer.of(ATTRIBUTES));
+
+                    return CompletableFuture.allOf(
+                            ensureRequiredPropertiesStage,
+                            ensureOnlyDefinedPropertiesStage,
+                            validatePropertiesStage
+                    );
+                }).orElseGet(DefaultWotThingModelValidation::success);
+    }
+
+    private CompletionStage enforceThingFeatures(final ThingSkeleton thingSkeleton,
+            final Thing thing,
+            final DittoHeaders dittoHeaders) {
+
+
+        return null;
+    }
+
+    private CompletableFuture ensureRequiredProperties(final ThingSkeleton thingSkeleton,
+            final DittoHeaders dittoHeaders, final Properties tdProperties, final JsonObject propertiesContainer,
+            final String containerName, final JsonPointer pointerPrefix) {
+
+        final CompletableFuture requiredPropertiesStage;
+        if (thingSkeleton instanceof ThingModel thingModel) {
+            final Set requiredProperties = extractRequiredProperties(tdProperties, thingModel);
+
+            propertiesContainer.getKeys().stream().map(JsonKey::toString).forEach(requiredProperties::remove);
+            if (!requiredProperties.isEmpty()) {
+                final var exceptionBuilder = WotThingModelPayloadValidationException
+                        .newBuilder("Required properties were missing from the Thing's " + containerName);
+                requiredProperties.forEach(rp ->
+                        exceptionBuilder.addValidationDetail(
+                                pointerPrefix.addLeaf(JsonKey.of(rp)),
+                                List.of(containerName + " <" + rp + "> is non optional and must be present")
+                        )
+                );
+                requiredPropertiesStage = CompletableFuture
+                        .failedFuture(exceptionBuilder.dittoHeaders(dittoHeaders).build());
+            } else {
+                requiredPropertiesStage = success();
+            }
+        } else {
+            requiredPropertiesStage = success();
+        }
+        return requiredPropertiesStage;
+    }
+
+    private CompletableFuture ensureOnlyDefinedProperties(final DittoHeaders dittoHeaders,
+            final Properties tdProperties, final JsonObject propertiesContainer, final String containerName) {
+        final CompletableFuture ensureOnlyDefinedPropertiesStage;
+        if (!validationConfig.getThingValidationConfig().isAllowNonModeledAttributes()) {
+            final Set allDefinedPropertyKeys = tdProperties.keySet();
+            final Set allAvailablePropertiesKeys =
+                    propertiesContainer.getKeys().stream().map(JsonKey::toString)
+                            .collect(Collectors.toCollection(LinkedHashSet::new));
+            allAvailablePropertiesKeys.removeAll(allDefinedPropertyKeys);
+            if (!allAvailablePropertiesKeys.isEmpty()) {
+                final var exceptionBuilder = WotThingModelPayloadValidationException
+                        .newBuilder("The Thing's " + containerName + " contained " + containerName +
+                                " keys which were not defined in the model: " + allAvailablePropertiesKeys);
+                ensureOnlyDefinedPropertiesStage = CompletableFuture.failedFuture(exceptionBuilder
+                        .dittoHeaders(dittoHeaders)
+                        .build());
+            } else {
+                ensureOnlyDefinedPropertiesStage = success();
+            }
+        } else {
+            ensureOnlyDefinedPropertiesStage = success();
+        }
+        return ensureOnlyDefinedPropertiesStage;
+    }
+
+    private CompletableFuture getValidatePropertiesStage(final DittoHeaders dittoHeaders,
+            final Properties tdProperties, final JsonObject propertiesContainer, final String containerName,
+            final JsonPointer pointerPrefix) {
+
+        final CompletableFuture validatePropertiesStage;
+        final Map invalidProperties =
+                determineInvalidProperties(tdProperties, propertiesContainer::getValue, dittoHeaders);
+        if (!invalidProperties.isEmpty()) {
+            final var exceptionBuilder = WotThingModelPayloadValidationException
+                    .newBuilder("The Thing's " + containerName + " contained validation errors, " +
+                            "check the validation details.");
+            invalidProperties.forEach((key, value) -> exceptionBuilder.addValidationDetail(
+                    pointerPrefix.addLeaf(JsonKey.of(key)),
+                    value.getDetails().stream()
+                            .map(ou -> ou.getInstanceLocation() + ": " + ou.getErrors())
+                            .toList()
+            ));
+            validatePropertiesStage = CompletableFuture.failedFuture(exceptionBuilder
+                    .dittoHeaders(dittoHeaders)
+                    .build());
+        } else {
+            validatePropertiesStage = success();
+        }
+        return validatePropertiesStage;
+    }
+
+    private Map determineInvalidProperties(final Properties tdProperties,
+            final Function> propertyExtractor, final DittoHeaders dittoHeaders) {
+
+        return tdProperties.entrySet().stream()
+                .flatMap(tdPropertyEntry ->
+                        propertyExtractor.apply(tdPropertyEntry.getKey())
+                                .map(attributeValue -> new AbstractMap.SimpleEntry<>(
+                                        tdPropertyEntry.getKey(),
+                                        jsonSchemaTools.validateDittoJsonBasedOnDataSchema(
+                                                tdPropertyEntry.getValue(),
+                                                attributeValue,
+                                                dittoHeaders
+                                        )
+                                ))
+                                .filter(entry -> !entry.getValue().isValid())
+                                .stream()
+                ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    @Override
+    public CompletionStage validateFeature(final ThingSkeleton thingSkeleton,
+            final Feature feature,
+            final DittoHeaders dittoHeaders) {
+        // TODO TJ implement
+        return success();
+    }
+
+    private static CompletableFuture success() {
+        return CompletableFuture.completedFuture(null);
+    }
+
+    private Set extractRequiredProperties(final Properties tdProperties, final ThingModel thingModel) {
+        return thingModel.getTmOptional().map(tmOptionalElements -> {
+            final Set allDefinedProperties = tdProperties.keySet();
+            final Set allOptionalProperties = tmOptionalElements.stream()
+                    .map(TmOptionalElement::toString)
+                    .filter(el -> el.startsWith("/properties/"))
+                    .map(el -> el.replace("/properties/", ""))
+                    .collect(Collectors.toSet());
+            final Set allRequiredProperties = new HashSet<>(allDefinedProperties);
+            allRequiredProperties.removeAll(allOptionalProperties);
+            return allRequiredProperties;
+        }).orElseGet(HashSet::new);
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaTools.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaTools.java
new file mode 100644
index 0000000000..b59748cac7
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaTools.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation;
+
+import java.io.IOException;
+
+import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.internal.utils.json.CborFactoryLoader;
+import org.eclipse.ditto.json.CborFactory;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.wot.model.SingleDataSchema;
+import org.eclipse.ditto.wot.model.WotInternalErrorException;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper;
+import com.networknt.schema.JsonMetaSchema;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.NonValidationKeyword;
+import com.networknt.schema.OutputFormat;
+import com.networknt.schema.PathType;
+import com.networknt.schema.SchemaId;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.output.OutputUnit;
+
+/**
+ * Contains tools around the used JsonSchema library and validating Ditto JSON, including mapping to Jackson.
+ */
+final class JsonSchemaTools {
+
+    private final CborFactory cborFactory;
+    private final ObjectMapper jacksonCborMapper;
+    private final SchemaValidatorsConfig schemaValidatorsConfig;
+
+    JsonSchemaTools() {
+        final var cborFactoryLoader = CborFactoryLoader.getInstance();
+        cborFactory = cborFactoryLoader.getCborFactoryOrThrow();
+        jacksonCborMapper = new CBORMapper();
+        schemaValidatorsConfig = new SchemaValidatorsConfig();
+        schemaValidatorsConfig.setPathType(PathType.JSON_POINTER);
+    }
+
+    JsonSchema extractFromSingleDataSchema(final SingleDataSchema dataSchema, final DittoHeaders dittoHeaders) {
+        final JsonNode jsonNode;
+        try {
+            final byte[] bytes = cborFactory.toByteArray(dataSchema.toJson());
+            jsonNode = jacksonCborMapper.reader().readTree(bytes);
+        } catch (final JsonParseException e) {
+            throw DittoRuntimeException.asDittoRuntimeException(e, t -> WotInternalErrorException.newBuilder()
+                            .message("Error during parsing input JSON")
+                            .cause(t)
+                            .dittoHeaders(dittoHeaders)
+                            .build() )
+                    .setDittoHeaders(dittoHeaders);
+        } catch (final IOException e) {
+            throw WotInternalErrorException.newBuilder()
+                    .cause(e)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+        final JsonMetaSchema.Builder metaSchemaBuilder = JsonMetaSchema.builder(SchemaId.V7, JsonMetaSchema.getV7());
+        metaSchemaBuilder.keyword(new NonValidationKeyword("@type"));
+        metaSchemaBuilder.keyword(new NonValidationKeyword("unit"));
+        return JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7, builder ->
+                        builder.metaSchema(metaSchemaBuilder.build()))
+                .getSchema(jsonNode, schemaValidatorsConfig);
+    }
+
+    OutputUnit validateDittoJsonBasedOnDataSchema(final SingleDataSchema dataSchema,
+            final JsonValue jsonValue,
+            final DittoHeaders dittoHeaders) {
+        final JsonSchema jsonSchema = extractFromSingleDataSchema(dataSchema, dittoHeaders);
+        return validateDittoJson(jsonSchema, jsonValue, dittoHeaders);
+    }
+
+    OutputUnit validateDittoJson(final JsonSchema jsonSchema,
+            final JsonValue jsonValue,
+            final DittoHeaders dittoHeaders) {
+        final JsonNode jsonNode;
+        try {
+            final byte[] bytes = cborFactory.toByteArray(jsonValue);
+            jsonNode = jacksonCborMapper.reader().readTree(bytes);
+        } catch (final JsonParseException e) {
+            throw DittoRuntimeException.asDittoRuntimeException(e, t -> WotInternalErrorException.newBuilder()
+                            .message("Error during parsing input JSON")
+                            .cause(t)
+                            .dittoHeaders(dittoHeaders)
+                            .build() )
+                    .setDittoHeaders(dittoHeaders);
+        } catch (final IOException e) {
+            throw WotInternalErrorException.newBuilder()
+                    .cause(e)
+                    .dittoHeaders(dittoHeaders)
+                    .build();
+        }
+        return jsonSchema.validate(jsonNode, OutputFormat.LIST);
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java
new file mode 100644
index 0000000000..ac8d1610c2
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation;
+
+import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
+
+import java.io.Serial;
+import java.net.URI;
+import java.util.AbstractMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.eclipse.ditto.base.model.common.HttpStatus;
+import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
+import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder;
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.base.model.json.FieldType;
+import org.eclipse.ditto.base.model.json.JsonParsableException;
+import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
+import org.eclipse.ditto.json.JsonCollectors;
+import org.eclipse.ditto.json.JsonFactory;
+import org.eclipse.ditto.json.JsonField;
+import org.eclipse.ditto.json.JsonFieldDefinition;
+import org.eclipse.ditto.json.JsonObject;
+import org.eclipse.ditto.json.JsonObjectBuilder;
+import org.eclipse.ditto.json.JsonPointer;
+import org.eclipse.ditto.json.JsonValue;
+import org.eclipse.ditto.wot.model.WotException;
+
+/**
+ * Exception thrown when a Ditto Thing (or parts of it), so the payload, could not be validated against the WoT Model.
+ *
+ * @since 3.6.0
+ */
+@Immutable
+@JsonParsableException(errorCode = WotThingModelPayloadValidationException.ERROR_CODE)
+public final class WotThingModelPayloadValidationException extends DittoRuntimeException implements WotException {
+
+    /**
+     * Error code of this exception.
+     */
+    public static final String ERROR_CODE = ERROR_CODE_PREFIX + "payload.validation.error";
+
+    private static final String DEFAULT_MESSAGE =
+            "The provided payload did not conform to the specified WoT (Web of Things) model.";
+
+    @Serial
+    private static final long serialVersionUID = -236554134452227841L;
+
+    private final Map> validationDetails;
+
+    private WotThingModelPayloadValidationException(final DittoHeaders dittoHeaders,
+            @Nullable final String message,
+            @Nullable final String description,
+            @Nullable final Throwable cause,
+            @Nullable final URI href,
+            final Map> validationDetails) {
+        super(ERROR_CODE, HttpStatus.BAD_REQUEST, dittoHeaders, message, description, cause, href);
+        this.validationDetails = validationDetails;
+    }
+
+    /**
+     * A mutable builder for a {@code WotThingModelPayloadValidationException}.
+     *
+     * @param validationDescription the details about what was not valid.
+     * @return the builder.
+     * @throws NullPointerException if {@code validationDescription} is {@code null}.
+     */
+    public static Builder newBuilder(final String validationDescription) {
+        return new Builder(validationDescription);
+    }
+
+    /**
+     * Constructs a new {@code WotThingModelPayloadValidationException} object with the exception message extracted from the given
+     * JSON object.
+     *
+     * @param jsonObject the JSON to read the {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException.JsonFields#MESSAGE} field from.
+     * @param dittoHeaders the headers of the command which resulted in this exception.
+     * @return the new WotThingModelPayloadValidationException.
+     * @throws NullPointerException if any argument is {@code null}.
+     * @throws org.eclipse.ditto.json.JsonMissingFieldException if this JsonObject did not contain an error message.
+     * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected
+     * format.
+     */
+    public static WotThingModelPayloadValidationException fromJson(final JsonObject jsonObject,
+            final DittoHeaders dittoHeaders) {
+
+        return DittoRuntimeException.fromJson(jsonObject, dittoHeaders,
+                new Builder(readValidationDetails(jsonObject))
+        );
+    }
+
+    @Override
+    public DittoRuntimeException setDittoHeaders(final DittoHeaders dittoHeaders) {
+        return new Builder(validationDetails)
+                .message(getMessage())
+                .description(getDescription().orElse(null))
+                .cause(getCause())
+                .href(getHref().orElse(null))
+                .dittoHeaders(dittoHeaders)
+                .build();
+    }
+
+    private static Map> readValidationDetails(final JsonObject jsonObject) {
+        checkNotNull(jsonObject, "JSON object");
+        return jsonObject.getValue(JsonFields.VALIDATION_DETAILS)
+                .map(validationDetails -> validationDetails.stream()
+                        .map(field -> new AbstractMap.SimpleEntry<>(
+                                JsonPointer.of(field.getKey().toString()),
+                                field.getValue().asArray().stream().map(JsonValue::formatAsString).toList())
+                        )
+                ).stream()
+                .flatMap(Function.identity())
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (u, v) -> {
+                    throw new IllegalStateException(String.format("Duplicate key %s", u));
+                }, LinkedHashMap::new));
+    }
+
+    @Override
+    protected void appendToJson(final JsonObjectBuilder jsonObjectBuilder, final Predicate predicate) {
+        final JsonObject detailsObject = validationDetails.entrySet().stream()
+                .map(entry -> JsonField.newInstance(entry.getKey().toString(),
+                        entry.getValue().stream()
+                                .map(JsonValue::of)
+                                .collect(JsonCollectors.valuesToArray())
+                ))
+                .collect(JsonCollectors.fieldsToObject());
+        if (!detailsObject.isEmpty()) {
+            jsonObjectBuilder.set(JsonFields.VALIDATION_DETAILS, detailsObject, predicate);
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        final WotThingModelPayloadValidationException that = (WotThingModelPayloadValidationException) o;
+        return Objects.equals(validationDetails, that.validationDetails);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), validationDetails);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" +
+                "message='" + getMessage() + '\'' +
+                ", errorCode=" + getErrorCode() +
+                ", httpStatus=" + getHttpStatus() +
+                ", description='" + getDescription().orElse(null) + '\'' +
+                ", href=" + getHref().orElse(null) +
+                ", validationDetails=" + validationDetails +
+                ", dittoHeaders=" + getDittoHeaders() +
+                ']';
+    }
+
+    /**
+     * A mutable builder with a fluent API for a {@link WotThingModelPayloadValidationException}.
+     */
+    @NotThreadSafe
+    public static final class Builder extends DittoRuntimeExceptionBuilder {
+
+        private Map> validationDetails;
+
+        private Builder() {
+            validationDetails = new LinkedHashMap<>();
+            message(DEFAULT_MESSAGE);
+        }
+
+        private Builder(final String validationDescription) {
+            this();
+            description(checkNotNull(validationDescription, "validationDescription"));
+        }
+
+        private Builder(final Map> validationDetails) {
+            this();
+            this.validationDetails = validationDetails;
+        }
+
+        public Builder addValidationDetail(final JsonPointer jsonPointer, final List validationErrors) {
+            validationDetails.put(jsonPointer, validationErrors);
+            return this;
+        }
+
+        @Override
+        protected WotThingModelPayloadValidationException doBuild(final DittoHeaders dittoHeaders,
+                @Nullable final String message,
+                @Nullable final String description,
+                @Nullable final Throwable cause,
+                @Nullable final URI href) {
+            return new WotThingModelPayloadValidationException(dittoHeaders, message, description, cause, href,
+                    validationDetails);
+        }
+
+    }
+
+    /**
+     * An enumeration of the known {@link org.eclipse.ditto.json.JsonField}s of a {@code WotThingModelPayloadValidationException}.
+     */
+    @Immutable
+    public static final class JsonFields {
+
+        /**
+         * JSON field containing the validation details.
+         */
+        static final JsonFieldDefinition VALIDATION_DETAILS =
+                JsonFactory.newJsonObjectFieldDefinition("validationDetails", FieldType.REGULAR,
+                        JsonSchemaVersion.V_2);
+
+        private JsonFields() {
+            throw new AssertionError();
+        }
+
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java
new file mode 100644
index 0000000000..efac7226a7
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation;
+
+import java.util.concurrent.CompletionStage;
+
+import org.eclipse.ditto.base.model.headers.DittoHeaders;
+import org.eclipse.ditto.things.model.Feature;
+import org.eclipse.ditto.things.model.Thing;
+import org.eclipse.ditto.wot.model.ThingSkeleton;
+import org.eclipse.ditto.wot.validation.config.TmValidationConfig;
+
+/**
+ * @since 3.6.0
+ */
+public interface WotThingModelValidation {
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateThing(ThingSkeleton thingSkeleton,
+            Thing thing,
+            DittoHeaders dittoHeaders);
+
+    /**
+     * TODO TJ doc
+     */
+    CompletionStage validateFeature(ThingSkeleton thingSkeleton,
+            Feature feature,
+            DittoHeaders dittoHeaders);
+
+    /**
+     * TODO TJ doc
+     * @return
+     */
+    static WotThingModelValidation createInstance(final TmValidationConfig validationConfig) {
+        return new DefaultWotThingModelValidation(validationConfig);
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java
new file mode 100644
index 0000000000..d1a3f34a1d
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation.config;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.KnownConfigValue;
+
+/**
+ * Provides configuration settings for WoT (Web of Things) based validation of Features.
+ *
+ * @since 3.6.0
+ */
+@Immutable
+public interface FeatureValidationConfig {
+
+    /**
+     * @return whether to enforce/validate properties of a feature following the defined WoT properties.
+     */
+    boolean isEnforceProperties();
+
+    /**
+     * @return whether to allow/accept persisting properties which are not defined as properties in the WoT model.
+     */
+    boolean isAllowNonModeledProperties();
+
+    /**
+     * @return whether to enforce/validate desired properties of a feature following the defined WoT properties.
+     */
+    boolean isEnforceDesiredProperties();
+
+    /**
+     * @return whether to allow/accept persisting desired properties which are not defined as properties in the WoT model.
+     */
+    boolean isAllowNonModeledDesiredProperties();
+
+    /**
+     * @return whether to enforce/validate inbox messages to a feature following the defined WoT actions.
+     */
+    boolean isEnforceInboxMessages();
+
+    /**
+     * @return whether to allow/accept dispatching of inbox messages which are not defined as actions in the WoT model.
+     */
+    boolean isAllowNonModeledInboxMessages();
+
+    /**
+     * @return whether to enforce/validate outbox messages from a feature following the defined WoT actions.
+     */
+    boolean isEnforceOutboxMessages();
+
+    /**
+     * @return whether to allow/accept dispatching of outbox messages which are not defined as actions in the WoT model.
+     */
+    boolean isAllowNonModeledOutboxMessages();
+
+
+    /**
+     * An enumeration of the known config path expressions and their associated default values for
+     * {@code FeatureValidationConfig}.
+     */
+    enum ConfigValue implements KnownConfigValue {
+
+        ENFORCE_PROPERTIES("enforce-properties", true),
+
+        ALLOW_NON_MODELED_PROPERTIES("allow-non-modeled-properties", false),
+
+        ENFORCE_DESIRED_PROPERTIES("enforce-desired-properties", true),
+
+        ALLOW_NON_MODELED_DESIRED_PROPERTIES("allow-non-modeled-desired-properties", false),
+
+        ENFORCE_INBOX_MESSAGES("enforce-inbox-messages", true),
+
+        ALLOW_NON_MODELED_INBOX_MESSAGES("allow-non-modeled-inbox-messages", false),
+
+        ENFORCE_OUTBOX_MESSAGES("enforce-outbox-messages", true),
+
+        ALLOW_NON_MODELED_OUTBOX_MESSAGES("allow-non-modeled-outbox-messages", false);
+
+
+        private final String path;
+        private final Object defaultValue;
+
+        ConfigValue(final String thePath, final Object theDefaultValue) {
+            path = thePath;
+            defaultValue = theDefaultValue;
+        }
+
+        @Override
+        public Object getDefaultValue() {
+            return defaultValue;
+        }
+
+        @Override
+        public String getConfigPath() {
+            return path;
+        }
+
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java
new file mode 100644
index 0000000000..ece0424636
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation.config;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.KnownConfigValue;
+
+/**
+ * Provides configuration settings for WoT (Web of Things) based validation of Things.
+ *
+ * @since 3.6.0
+ */
+@Immutable
+public interface ThingValidationConfig {
+
+    /**
+     * @return whether to enforce/validate attributes of a thing following the defined WoT properties.
+     */
+    boolean isEnforceAttributes();
+
+    /**
+     * @return whether to allow/accept persisting attributes which are not defined as properties in the WoT model.
+     */
+    boolean isAllowNonModeledAttributes();
+
+    /**
+     * @return whether to enforce/validate inbox messages to a thing following the defined WoT actions.
+     */
+    boolean isEnforceInboxMessages();
+
+    /**
+     * @return whether to allow/accept dispatching of inbox messages which are not defined as actions in the WoT model.
+     */
+    boolean isAllowNonModeledInboxMessages();
+
+    /**
+     * @return whether to enforce/validate outbox messages from a thing following the defined WoT actions.
+     */
+    boolean isEnforceOutboxMessages();
+
+    /**
+     * @return whether to allow/accept dispatching of outbox messages which are not defined as actions in the WoT model.
+     */
+    boolean isAllowNonModeledOutboxMessages();
+
+
+    /**
+     * An enumeration of the known config path expressions and their associated default values for
+     * {@code ThingValidationConfig}.
+     */
+    enum ConfigValue implements KnownConfigValue {
+
+        ENFORCE_ATTRIBUTES("enforce-attributes", true),
+
+        ALLOW_NON_MODELED_ATTRIBUTES("allow-non-modeled-attributes", false),
+
+        ENFORCE_INBOX_MESSAGES("enforce-inbox-messages", true),
+
+        ALLOW_NON_MODELED_INBOX_MESSAGES("allow-non-modeled-inbox-messages", false),
+
+        ENFORCE_OUTBOX_MESSAGES("enforce-outbox-messages", true),
+
+        ALLOW_NON_MODELED_OUTBOX_MESSAGES("allow-non-modeled-outbox-messages", false);
+
+
+        private final String path;
+        private final Object defaultValue;
+
+        ConfigValue(final String thePath, final Object theDefaultValue) {
+            path = thePath;
+            defaultValue = theDefaultValue;
+        }
+
+        @Override
+        public Object getDefaultValue() {
+            return defaultValue;
+        }
+
+        @Override
+        public String getConfigPath() {
+            return path;
+        }
+
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java
new file mode 100644
index 0000000000..e7afc69530
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.wot.validation.config;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.eclipse.ditto.internal.utils.config.KnownConfigValue;
+
+/**
+ * Provides configuration settings for WoT (Web of Things) integration regarding the validation of Things and Features
+ * based on their WoT ThingModels.
+ *
+ * @since 3.6.0
+ */
+@Immutable
+public interface TmValidationConfig {
+
+    /**
+     * @return whether the ThingModel validation of Things/Features should be enabled or not.
+     */
+    boolean isEnabled();
+
+    /**
+     * @return the config for validating things.
+     */
+    ThingValidationConfig getThingValidationConfig();
+
+    /**
+     * @return the config for validating features.
+     */
+    FeatureValidationConfig getFeatureValidationConfig();
+
+
+    /**
+     * An enumeration of the known config path expressions and their associated default values for
+     * {@code TmValidationConfig}.
+     */
+    enum ConfigValue implements KnownConfigValue {
+
+        /**
+         * Whether the TM based validation should be enabled or not.
+         */
+        ENABLED("enabled", true);
+
+
+        private final String path;
+        private final Object defaultValue;
+
+        ConfigValue(final String thePath, final Object theDefaultValue) {
+            path = thePath;
+            defaultValue = theDefaultValue;
+        }
+
+        @Override
+        public Object getDefaultValue() {
+            return defaultValue;
+        }
+
+        @Override
+        public String getConfigPath() {
+            return path;
+        }
+
+    }
+}
diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java
new file mode 100644
index 0000000000..e472596f25
--- /dev/null
+++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+/**
+ * @since 3.6.0
+ */
+@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
+package org.eclipse.ditto.wot.validation;
\ No newline at end of file