From 723063891b2db6111a8de2795bfc78767d69e311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Tue, 20 Jun 2023 16:31:56 +0200 Subject: [PATCH] #1650 provide WoT TM validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * restructuring of "ditto-wot" module to enable re-usability of non-pekko/non-Ditto specifics * adding "validator" concept and first sample implementation (WIP) Signed-off-by: Thomas Jäckle --- .../SubscribeForPersistedEvents.java | 5 - base/service/pom.xml | 4 + .../ditto/base/service/DittoService.java | 2 +- bom/pom.xml | 41 ++- connectivity/service/pom.xml | 10 +- .../service/config/DefaultHttpPushConfig.java | 4 +- .../service/config/HttpPushConfig.java | 2 +- .../httppush/HttpPushClientActor.java | 4 +- .../src/main/resources/connectivity.conf | 16 +- ...ctivityServiceGlobalErrorRegistryTest.java | 4 +- .../config/DefaultHttpPushConfigTest.java | 2 +- .../httppush/HttpPushFactoryTest.java | 44 +-- gateway/service/pom.xml | 4 + .../config/security/AuthenticationConfig.java | 2 +- .../security/DefaultAuthenticationConfig.java | 4 +- .../service/src/main/resources/gateway.conf | 6 +- .../service/endpoints/EndpointTestBase.java | 2 +- ...GatewayServiceGlobalErrorRegistryTest.java | 4 +- .../DefaultAuthenticationConfigTest.java | 2 +- internal/utils/cache-loaders/pom.xml | 5 + .../src/main/resources/reference.conf | 2 +- internal/utils/cluster/pom.xml | 4 + .../cluster/CborJsonValueSerializer.java | 14 +- .../cluster/CborJsonifiableSerializer.java | 4 +- .../cluster/CborJsonValueSerializerTest.java | 7 +- internal/utils/config/pom.xml | 12 - .../http/DefaultHttpProxyBaseConfig.java | 125 +++++++ .../config/http/HttpProxyBaseConfig.java | 16 +- .../main/resources/ditto-pekko-config.conf | 6 +- .../resources/ditto-things-aggregator.conf | 2 +- .../http/DefaultHttpProxyBaseConfigTest.java | 94 +++++ .../src/test/resources/http-proxy-test.conf | 9 + .../utils/http/DefaultHttpClientFacade.java | 12 +- .../http/config}/DefaultHttpProxyConfig.java | 9 +- .../utils/http/config/HttpProxyConfig.java | 34 ++ .../config}/DefaultHttpProxyConfigTest.java | 36 +- internal/utils/json/pom.xml | 38 ++ .../utils/json}/CborFactoryLoader.java | 10 +- .../utils/json}/CborFactoryLoaderTest.java | 4 +- internal/utils/metrics-service/pom.xml | 66 ++++ ...edForkJoinExecutorServiceConfigurator.java | 11 +- ...ThreadPoolExecutorServiceConfigurator.java | 11 +- .../service}/mongo/MongoMetricsBuilder.java | 4 +- .../prometheus/PrometheusReporterRoute.java | 5 +- internal/utils/metrics/pom.xml | 34 +- .../config/DefaultMetricsConfigTest.java | 2 +- .../instruments/counter/KamonCounterTest.java | 2 +- .../instruments/gauge/KamonGaugeTest.java | 2 +- .../histogram/KamonHistogramTest.java | 2 +- .../tag/KamonTagSetConverterTest.java | 2 +- .../metrics/instruments/tag/TagSetTest.java | 2 +- .../metrics/instruments/tag/TagTest.java | 2 +- .../timer/PreparedKamonTimerTest.java | 2 +- .../instruments/timer/StartInstantTest.java | 2 +- .../timer/StartedKamonTimerTest.java | 2 +- .../timer/StoppedKamonTimerTest.java | 2 +- .../persistentactors/cleanup/Credits.java | 10 +- internal/utils/pom.xml | 2 + internal/utils/protocol/pom.xml | 5 + .../cbor/JacksonSerializationContext.java | 2 +- .../src/main/resources/reference.conf | 4 +- .../service/src/main/resources/policies.conf | 6 +- things/service/pom.xml | 4 + .../common/config/DittoThingsConfig.java | 4 +- .../service/common/config/ThingsConfig.java | 2 +- .../AbstractThingCommandStrategy.java | 15 +- .../commands/CreateThingStrategy.java | 11 +- .../commands/DeleteAttributeStrategy.java | 13 +- .../commands/DeleteAttributesStrategy.java | 11 +- .../DeleteFeatureDefinitionStrategy.java | 11 +- ...eleteFeatureDesiredPropertiesStrategy.java | 9 +- .../DeleteFeatureDesiredPropertyStrategy.java | 13 +- .../DeleteFeaturePropertiesStrategy.java | 11 +- .../DeleteFeaturePropertyStrategy.java | 13 +- .../commands/DeleteFeatureStrategy.java | 11 +- .../commands/DeleteFeaturesStrategy.java | 11 +- .../DeleteThingDefinitionStrategy.java | 11 +- .../commands/DeleteThingStrategy.java | 11 +- .../commands/MergeThingStrategy.java | 7 +- .../commands/ModifyAttributeStrategy.java | 17 +- .../commands/ModifyAttributesStrategy.java | 13 +- .../ModifyFeatureDefinitionStrategy.java | 11 +- ...odifyFeatureDesiredPropertiesStrategy.java | 13 +- .../ModifyFeatureDesiredPropertyStrategy.java | 17 +- .../ModifyFeaturePropertiesStrategy.java | 13 +- .../ModifyFeaturePropertyStrategy.java | 17 +- .../commands/ModifyFeatureStrategy.java | 71 ++-- .../commands/ModifyFeaturesStrategy.java | 13 +- .../commands/ModifyPolicyIdStrategy.java | 7 +- .../ModifyThingDefinitionStrategy.java | 11 +- .../commands/ModifyThingStrategy.java | 28 +- .../commands/RetrieveAttributeStrategy.java | 15 +- .../commands/RetrieveAttributesStrategy.java | 13 +- .../RetrieveFeatureDefinitionStrategy.java | 11 +- ...rieveFeatureDesiredPropertiesStrategy.java | 13 +- ...etrieveFeatureDesiredPropertyStrategy.java | 15 +- .../RetrieveFeaturePropertiesStrategy.java | 13 +- .../RetrieveFeaturePropertyStrategy.java | 15 +- .../commands/RetrieveFeatureStrategy.java | 8 +- .../commands/RetrieveFeaturesStrategy.java | 7 +- .../commands/RetrievePolicyIdStrategy.java | 7 +- .../RetrieveThingDefinitionStrategy.java | 9 +- .../commands/RetrieveThingStrategy.java | 8 +- .../commands/SudoRetrieveThingStrategy.java | 7 +- .../commands/ThingCommandStrategies.java | 96 ++--- .../commands/ThingConflictStrategy.java | 7 +- things/service/src/main/resources/things.conf | 40 ++- .../commands/AbstractCommandStrategyTest.java | 13 + .../commands/DeleteAttributeStrategyTest.java | 6 +- .../DeleteAttributesStrategyTest.java | 6 +- .../DeleteFeatureDefinitionStrategyTest.java | 6 +- ...eFeatureDesiredPropertiesStrategyTest.java | 6 +- ...eteFeatureDesiredPropertyStrategyTest.java | 6 +- .../DeleteFeaturePropertiesStrategyTest.java | 6 +- .../DeleteFeaturePropertyStrategyTest.java | 6 +- .../commands/DeleteFeatureStrategyTest.java | 7 +- .../commands/DeleteFeaturesStrategyTest.java | 6 +- .../DeleteThingDefinitionStrategyTest.java | 6 +- .../commands/DeleteThingStrategyTest.java | 6 +- .../commands/MergeThingStrategyTest.java | 6 +- .../commands/ModifyAttributeStrategyTest.java | 6 +- .../ModifyAttributesStrategyTest.java | 8 +- .../ModifyFeatureDefinitionStrategyTest.java | 6 +- ...yFeatureDesiredPropertiesStrategyTest.java | 6 +- ...ifyFeatureDesiredPropertyStrategyTest.java | 7 +- .../ModifyFeaturePropertiesStrategyTest.java | 6 +- .../ModifyFeaturePropertyStrategyTest.java | 7 +- .../commands/ModifyFeatureStrategyTest.java | 9 +- .../commands/ModifyFeaturesStrategyTest.java | 7 +- .../commands/ModifyPolicyIdStrategyTest.java | 6 +- .../ModifyThingDefinitionStrategyTest.java | 6 +- .../commands/ModifyThingStrategyTest.java | 8 +- .../RetrieveAttributeStrategyTest.java | 6 +- .../RetrieveAttributesStrategyTest.java | 6 +- ...RetrieveFeatureDefinitionStrategyTest.java | 6 +- ...eFeatureDesiredPropertiesStrategyTest.java | 6 +- ...eveFeatureDesiredPropertyStrategyTest.java | 6 +- ...RetrieveFeaturePropertiesStrategyTest.java | 6 +- .../RetrieveFeaturePropertyStrategyTest.java | 6 +- .../commands/RetrieveFeatureStrategyTest.java | 7 +- .../RetrieveFeaturesStrategyTest.java | 6 +- .../RetrievePolicyIdStrategyTest.java | 6 +- .../RetrieveThingDefinitionStrategyTest.java | 6 +- .../commands/RetrieveThingStrategyTest.java | 7 +- .../SudoRetrieveThingStrategyTest.java | 6 +- .../commands/ThingConflictStrategyTest.java | 9 +- .../ThingsServiceGlobalErrorRegistryTest.java | 4 +- .../service/src/main/resources/search.conf | 8 +- wot/api/pom.xml | 84 +++++ .../DefaultFeatureValidationConfig.java | 148 ++++++++ .../config/DefaultThingValidationConfig.java | 127 +++++++ .../config/DefaultTmBasedCreationConfig.java | 4 +- .../config/DefaultTmScopedCreationConfig.java | 4 +- .../api/config/DefaultTmValidationConfig.java | 99 ++++++ .../DefaultToThingDescriptionConfig.java | 4 +- .../wot/api}/config/DefaultWotConfig.java | 35 +- .../api}/config/TmBasedCreationConfig.java | 4 +- .../api}/config/TmScopedCreationConfig.java | 4 +- .../api}/config/ToThingDescriptionConfig.java | 4 +- .../ditto/wot/api}/config/WotConfig.java | 18 +- .../ditto/wot/api}/config/package-info.java | 6 +- .../DefaultWotThingDescriptionGenerator.java | 172 +++++++-- ...DefaultWotThingModelExtensionResolver.java | 9 +- .../DefaultWotThingSkeletonGenerator.java | 267 +++++++++----- .../wot/api}/generator/DittoWotExtension.java | 4 +- .../WotThingDescriptionGenerator.java} | 88 +++-- .../WotThingModelExtensionResolver.java | 19 +- .../generator/WotThingSkeletonGenerator.java | 59 +++- .../wot/api}/generator/package-info.java | 6 +- .../provider/DefaultWotThingModelFetcher.java | 112 ++++++ .../wot/api/provider/JsonDownloader.java | 38 ++ .../api}/provider/WotThingModelFetcher.java | 24 +- .../ditto/wot/api}/provider/package-info.java | 6 +- .../DefaultWotThingModelResolver.java | 165 +++++++++ .../ditto/wot/api/resolver/ThingSubmodel.java | 25 ++ .../api/resolver/WotThingModelResolver.java | 97 +++++ .../ditto/wot/api/resolver/package-info.java | 20 ++ .../DefaultWotThingModelValidator.java | 126 +++++++ .../api/validator/WotThingModelValidator.java | 65 ++++ .../ditto/wot/api/validator/package-info.java | 18 + wot/integration/pom.xml | 14 +- .../DefaultDittoWotIntegration.java | 134 +++++++ .../wot/integration/DittoWotIntegration.java | 80 +++++ ...cher.java => PekkoHttpJsonDownloader.java} | 119 ++----- .../WotThingDescriptionGenerator.java | 80 ----- .../ditto/wot/integration/package-info.java | 19 + .../DefaultWotThingDescriptionProvider.java | 332 ------------------ wot/model/pom.xml | 1 + wot/pom.xml | 5 +- wot/validation/pom.xml | 71 ++++ .../DefaultWotThingModelValidation.java | 233 ++++++++++++ .../ditto/wot/validation/JsonSchemaTools.java | 112 ++++++ ...tThingModelPayloadValidationException.java | 243 +++++++++++++ .../validation/WotThingModelValidation.java | 49 +++ .../config/FeatureValidationConfig.java | 110 ++++++ .../config/ThingValidationConfig.java | 96 +++++ .../validation/config/TmValidationConfig.java | 75 ++++ .../ditto/wot/validation/package-info.java | 18 + 198 files changed, 4112 insertions(+), 1279 deletions(-) create mode 100644 internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java rename base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java => internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java (83%) create mode 100644 internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java create mode 100644 internal/utils/config/src/test/resources/http-proxy-test.conf rename {base/service/src/main/java/org/eclipse/ditto/base/service/config/http => internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config}/DefaultHttpProxyConfig.java (97%) create mode 100644 internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/config/HttpProxyConfig.java rename {base/service/src/test/java/org/eclipse/ditto/base/service/config/http => internal/utils/http/src/test/java/org/eclipse/ditto/internal/utils/http/config}/DefaultHttpProxyConfigTest.java (62%) create mode 100755 internal/utils/json/pom.xml rename internal/utils/{cluster/src/main/java/org/eclipse/ditto/internal/utils/cluster => json/src/main/java/org/eclipse/ditto/internal/utils/json}/CborFactoryLoader.java (90%) rename internal/utils/{cluster/src/test/java/org/eclipse/ditto/internal/utils/cluster => json/src/test/java/org/eclipse/ditto/internal/utils/json}/CborFactoryLoaderTest.java (91%) create mode 100644 internal/utils/metrics-service/pom.xml rename internal/utils/{metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics => metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service}/executor/InstrumentedForkJoinExecutorServiceConfigurator.java (89%) rename internal/utils/{metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics => metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service}/executor/InstrumentedThreadPoolExecutorServiceConfigurator.java (89%) rename internal/utils/{metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics => metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service}/mongo/MongoMetricsBuilder.java (94%) rename internal/utils/{metrics/src/main/java/org/eclipse/ditto/internal/utils/metrics => metrics-service/src/main/java/org/eclipse/ditto/internal/utils/metrics/service}/prometheus/PrometheusReporterRoute.java (93%) create mode 100755 wot/api/pom.xml create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultTmBasedCreationConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultTmScopedCreationConfig.java (96%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultToThingDescriptionConfig.java (97%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultWotConfig.java (74%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/TmBasedCreationConfig.java (94%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/TmScopedCreationConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/ToThingDescriptionConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/WotConfig.java (70%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/package-info.java (80%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingDescriptionGenerator.java (86%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingModelExtensionResolver.java (97%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingSkeletonGenerator.java (68%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DittoWotExtension.java (91%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java => api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java} (50%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/WotThingModelExtensionResolver.java (87%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/WotThingSkeletonGenerator.java (69%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/package-info.java (79%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/provider/WotThingModelFetcher.java (73%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/provider/package-info.java (80%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/ThingSubmodel.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java rename wot/integration/src/main/java/org/eclipse/ditto/wot/integration/{provider/DefaultWotThingModelFetcher.java => PekkoHttpJsonDownloader.java} (56%) delete mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java delete mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java create mode 100644 wot/validation/pom.xml create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaTools.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java 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 d41b6f7d570..9ee3dd0cc25 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 566bbb9f208..4428d44269f 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 2dd6e3e3683..01f8ea9d1fb 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 218f0cf1950..1670ff851ad 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 fe4984a3cf0..afd3837629b 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 89e75ba647e..334ec3fa146 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 35cd538632f..06eaa7b7cf4 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 36ca0bd0249..c22a436a708 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 0b6fea9c841..daa7284973a 100644 --- a/connectivity/service/src/main/resources/connectivity.conf +++ b/connectivity/service/src/main/resources/connectivity.conf @@ -1240,7 +1240,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 @@ -1261,7 +1261,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 @@ -1276,17 +1276,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. @@ -1300,17 +1300,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 a0eaf38f37c..4f71b563248 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 6880e520115..7db14ee2cc1 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 7517bda751a..a7c37775fe7 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 bdb9d688095..3bd8277820d 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 7ce4458a28b..41383048f28 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 ccf2840723c..226e8503433 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 c50e7d36694..4ce3b857f7a 100755 --- a/gateway/service/src/main/resources/gateway.conf +++ b/gateway/service/src/main/resources/gateway.conf @@ -457,7 +457,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 { @@ -586,7 +586,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 @@ -602,7 +602,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 3a736855777..a02e1e72bab 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; @@ -76,6 +75,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 a1f61db9ea9..939a85a15e5 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 0586b53781d..de08037daef 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 ccca6698434..866f78907f0 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 822e734be21..6d2978e2140 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 ff46868b7b1..ae29b2ab89b 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 49486887ff9..c92143b3255 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 f78ddaa1167..7a6e931c950 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 30ad3015a31..3c7d92dce65 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 aebbdf6b16c..02078c21484 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 00000000000..5ee63c22a2b --- /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 8a67baed539..93af767e595 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 f32d27946f3..0174e5d62df 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 84652374347..be5714d3c71 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 00000000000..c34a4c0590f --- /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/internal/utils/config/src/test/resources/http-proxy-test.conf b/internal/utils/config/src/test/resources/http-proxy-test.conf new file mode 100644 index 00000000000..2d51e8eeb74 --- /dev/null +++ b/internal/utils/config/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/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 b0719d9787c..7b5bccea7e1 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 1045897c1d1..11bdd23281b 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 00000000000..30eb3d7ff73 --- /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 ee956c49971..de17c920b4d 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/json/pom.xml b/internal/utils/json/pom.xml new file mode 100755 index 00000000000..69c9b9a1066 --- /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 e11e3ba6eb1..44dee1c64e8 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 782757364e2..d9b6c528302 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 00000000000..7caf2fd2684 --- /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 c6df458e439..32d90ef7ab8 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 47467e4b082..a7ae657f636 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 1b236b3ae04..c738e028bd7 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 bb8163089c5..39a89b48745 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 8086c514adb..f6a47b225cc 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 d8410f92774..eca88e7ad0d 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 8b41794d207..ee78c7a14af 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 cd20c786b87..f7c11a6a129 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 1d6df9a49a3..f4433608834 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 848e5fc8e69..e3092ec4927 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 ec7d91724e4..f947c6fac09 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 09b85a03be2..172ca79dcc0 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 70f19ae9191..3908bd6e1aa 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 fbb4f937e4d..65935450776 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 f926d748486..e060d368610 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 6e62efc0445..0afbee33b86 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 6ad4112bc90..f8e6ce48661 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 541e144da9c..af8f64bd7d7 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 56ba2d96ba4..536975e372d 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 853509dbeb5..0ef662ee9b9 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 6cc7c165133..1b623b87cd6 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 2f1c90e23e9..0b2ec8e37f9 100755
--- a/policies/service/src/main/resources/policies.conf
+++ b/policies/service/src/main/resources/policies.conf
@@ -306,7 +306,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
@@ -321,7 +321,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
@@ -333,7 +333,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 49013ab40cc..0383a0c0072 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 7570d5bcc6b..611ea6ee930 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 0e26ac4b463..327e942eeb8 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 ea04c8b4f0d..67360f2d98a 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 7f34c850ca9..a9ad37378f2 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 8a1f6d98911..f7f11bc1258 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 4baca49012e..dc38fba537a 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 eb60ba43094..d08ac878b5b 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 619214bf168..7bdc0cbba4e 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 9a689a58a9b..077099779de 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 b2b38c3d428..ecc943efe39 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 0bbaa07d82a..b29fcb3671b 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 2b1d2e40e61..1baee618991 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 c72feb488f8..7cf677a8ad6 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 330c34064a2..2869340b4b7 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 7f0f2ec2f6d..0fe27117847 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 e3d37d51e29..7a2b350c8b6 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 28b0b411320..110237cd2e1 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 05558aa6d0b..070c4b69547 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 d2bf551920d..edf2a460647 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 7f8057996f8..0ea5a429998 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 bf97b625be7..a8f83a4068c 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 e616ebbeaad..119c96eab7c 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 9b615559a35..00a59859f76 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 d5c31beb638..d81690ac85f 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 b997b026e8b..4acb121b273 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 37327fde0ff..c9fea3bbaa9 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 a2744503662..d62f1d08480 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 83176657841..1d8ec48a575 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 4f7371f382b..70cedc9d438 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 aa39f30fada..5e47b6023a6 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 0c0447e72f2..7f274941bfc 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 cf6f35cb9f9..fa1899a1e4f 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 9e04f3b4b79..f07aaf03809 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 96e22b7afc6..6c074173522 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 e8eb14f6303..a02e1c6d1cf 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 2f050c3d877..452d9491718 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 ec4013a10da..6e68cda0aec 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 fdd5b3fde05..5a5a3bff02b 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 0c146290585..777c780770d 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 91decb307fe..836f1f3305e 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 02a84abc0d2..cad6dc57635 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 71470f2fed7..af4b45b0ead 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 8e6f26e8153..9930ce8188e 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 8ce41cb94b8..7f2661bdff7 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 4091d57d4df..968341f33f6 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 fcca0b43aad..404cc99dbac 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 663096f71cf..6b7482bbeb4 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 81d1cf8d369..8f517fc7561 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 6c5105f6fba..59966e17bfc 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 454548ba816..33513afca90 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 2923fe26449..21a8a341c7a 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 945f66f6f01..c19d3dfe85c 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 00000000000..1a486e08e7c
--- /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 00000000000..63f131bf192
--- /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 00000000000..cf04dc43584
--- /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 1039a202eff..25cefa6b87b 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 f56139c3a09..4f0640ea0ec 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 00000000000..1c082826266
--- /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 172775dbd36..92582a4d4c0 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 052dc3a7a5c..63c8ac07644 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 564bb98c784..a4754ebb4f6 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 9735b76808d..bdd1cb6f719 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 9f591aebb68..d68183cc88b 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 c36b357efd9..3b64d290c83 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 c8a84b71858..92fb86e9274 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 a687ba9096c..5a778fd0c6c 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;
@@ -39,8 +41,6 @@
 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;
@@ -48,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;
@@ -90,15 +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.apache.pekko.actor.ActorSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default Ditto specific implementation of {@link WotThingDescriptionGenerator}.
@@ -106,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 =
@@ -124,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
@@ -145,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);
@@ -184,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 631d2673d86..231c453e3c9 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 32534431ff1..9c76149d888 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 26453f567b5..68a9fcfef56 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 3b7a7c83beb..706d25dcdd8 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 38349e5409e..c0aee200dd3 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 c9c724d5081..517bbadcf43 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 b37be650e03..bb7b5808abe 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 00000000000..7bb4ba74200
--- /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 00000000000..0d94f486e02
--- /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 bc9608a3c99..5c5023fcafa 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 0ab8bd7d389..e479d99dc33 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 00000000000..fcca26ce595
--- /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 00000000000..477d9d20d3a
--- /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 00000000000..ca7209c4639
--- /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 00000000000..8679aa4d8a4
--- /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 00000000000..4e50d6c554c
--- /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 00000000000..3c5bd5b9a07
--- /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 00000000000..236a0d49dc0
--- /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 d1552409c04..44146afb071 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 00000000000..1230eeac3ab
--- /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 00000000000..dc912b92148
--- /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 765897006ab..342d184ce9c 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 b3eb6ad9ebc..00000000000
--- 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 00000000000..c7e6892b611
--- /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 4e51f11e45d..00000000000
--- 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 9ea16a36279..6c3f4fe0ffd 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 4847a056177..93d142b8b78 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 00000000000..a18124286bc
--- /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 00000000000..b59748cac7d
--- /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 00000000000..ac8d1610c20
--- /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 00000000000..efac7226a7a
--- /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 00000000000..d1a3f34a1d8
--- /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 00000000000..ece0424636f
--- /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 00000000000..e7afc69530c
--- /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 00000000000..e472596f256
--- /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