UNOMI-568: refacto json-schema implementation (#421)

* UNOMI-568: refacto json-schema implementation

* UNOMI-568: code review handling comments and suggestions

* fix object mapper in integration tests

* Fail safe on missing schema + fix timestamp schema

* clean up test objects

* Set pom description to JSON Schema projects

* Send an error when a predefined schema is override.

* update code ql to v2

* provide codeql config to ignore itests folder in order to reduce result file

* set codeql to debug

* split codeQL workflows to have a dedicated java build

* do not upload file SARIF to github

Co-authored-by: David Griffon <dgriffon@jahia.com>
diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml
new file mode 100644
index 0000000..df45fcf
--- /dev/null
+++ b/.github/codeql/codeql-config.yml
@@ -0,0 +1,4 @@
+name: "Unomi CodeQL config"
+
+paths-ignore:
+  - itests
\ No newline at end of file
diff --git a/.github/workflows/codeql-analysis-java.yml b/.github/workflows/codeql-analysis-java.yml
new file mode 100644
index 0000000..7e46387
--- /dev/null
+++ b/.github/workflows/codeql-analysis-java.yml
@@ -0,0 +1,47 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL JAVA"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '38 1 * * 0'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v2
+      with:
+        config-file: ./.github/codeql/codeql-config.yml
+        languages: java
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    - run: mvn -U -ntp -e clean install -DskipTests
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v2
+      with:
+        upload: false
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis-javascript.yml
similarity index 91%
rename from .github/workflows/codeql-analysis.yml
rename to .github/workflows/codeql-analysis-javascript.yml
index f631368..ce6239f 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis-javascript.yml
@@ -9,7 +9,7 @@
 # the `language` matrix defined below to confirm you have the correct set of
 # supported CodeQL languages.
 #
-name: "CodeQL"
+name: "CodeQL Javascript"
 
 on:
   push:
@@ -28,7 +28,7 @@
     strategy:
       fail-fast: false
       matrix:
-        language: [ 'java', 'javascript' ]
+        language: [ 'javascript' ]
         # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
         # Learn more:
         # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
@@ -39,7 +39,7 @@
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +50,7 @@
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # ℹī¸ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -64,4 +64,4 @@
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2
diff --git a/api/src/main/java/org/apache/unomi/api/schema/JSONSchemaExtension.java b/api/src/main/java/org/apache/unomi/api/schema/JSONSchemaExtension.java
deleted file mode 100644
index 652102d..0000000
--- a/api/src/main/java/org/apache/unomi/api/schema/JSONSchemaExtension.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.unomi.api.schema;
-
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.MetadataItem;
-
-/**
- * Object which represents a JSON schema extensions stored in the persistence service
- */
-public class JSONSchemaExtension extends MetadataItem {
-    public static final String ITEM_TYPE = "jsonSchemaExtension";
-
-    private String id;
-    private String extension;
-    private double priority;
-    private String schemaId;
-
-    public JSONSchemaExtension() {
-    }
-
-    /**
-     * Instantiates a new JSON schema with an id and a schema extension as string
-     *
-     * @param id        id of the extension
-     * @param schemaId  id of the schema
-     * @param extension as string
-     * @param priority  priority to process the extension
-     */
-    public JSONSchemaExtension(String id, String schemaId, String extension, float priority) {
-        super(new Metadata(id));
-        this.id = id;
-        this.extension = extension;
-        this.priority = priority;
-        this.schemaId = schemaId;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public String getExtension() {
-        return extension;
-    }
-
-    public void setExtension(String extension) {
-        this.extension = extension;
-    }
-
-    public double getPriority() {
-        return priority;
-    }
-
-    public void setPriority(double priority) {
-        this.priority = priority;
-    }
-
-    public String getSchemaId() {
-        return schemaId;
-    }
-
-    public void setSchemaId(String schemaId) {
-        this.schemaId = schemaId;
-    }
-}
diff --git a/api/src/main/java/org/apache/unomi/api/services/SchemaService.java b/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
deleted file mode 100644
index 7d6e654..0000000
--- a/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.unomi.api.services;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.schema.json.JSONSchema;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Service that allow to manage JSON schema. It allows to get, save and delete schemas
- */
-public interface SchemaService {
-
-    /**
-     * Retrieves json schema metadatas, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them
-     * are retrieved, starting with the {@code
-     * offset}-th one.
-     *
-     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
-     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
-     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the
-     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
-     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
-     * @return a {@link PartialList} of json schema metadata
-     */
-    PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy);
-
-    /**
-     * Verify if a jsonNode is valid against a schema
-     *
-     * @param jsonNode   to validate
-     * @param schemaId id of the schema used for the validation
-     * @return true is the object is valid
-     */
-    boolean isValid(JsonNode jsonNode, String schemaId);
-
-    /**
-     * Get a schema matching by a schema id
-     *
-     * @param schemaId Id of the schema
-     * @return A JSON schema
-     */
-    JSONSchema getSchema(String schemaId);
-
-    /**
-     * Get a list a {@link org.apache.unomi.api.schema.json.JSONSchema}
-     *
-     * @param target to filter the schemas
-     * @return a list of JSONSchema
-     */
-    List<JSONSchema> getSchemasByTarget(String target);
-
-    /**
-     * Save a new schema or update a schema
-     *
-     * @param schema as a String value
-     */
-    void saveSchema(String schema);
-
-    /**
-     * Save a new schema or update a schema
-     *
-     * @param schemaStream inputStream of the schema
-     */
-    void saveSchema(InputStream schemaStream) throws IOException;
-
-    /**
-     * Load a predefined schema into memory
-     *
-     * @param schemaStream inputStream of the schema
-     */
-    void loadPredefinedSchema(InputStream schemaStream);
-
-    /**
-     * Delete a schema according to its id
-     *
-     * @param schemaId id of the schema to delete
-     * @return true if the schema has been deleted
-     */
-    boolean deleteSchema(String schemaId);
-
-    /**
-     * Delete a schema
-     *
-     * @param schemaStream inputStream of the schema to delete
-     * @return true if the schema has been deleted
-     */
-    boolean deleteSchema(InputStream schemaStream);
-
-    /**
-     * Save an extension of a JSON schema
-     *
-     * @param extensionStream inputStream of the extension
-     */
-    void saveExtension(InputStream extensionStream) throws IOException;
-
-    /**
-     * Save an extension of a JSON schema
-     *
-     * @param extension as String value
-     */
-    void saveExtension(String extension) throws IOException;
-
-    /**
-     * Delete an extension
-     *
-     * @param extensionStream inputStream of the extension to delete
-     * @return true if the extension has been deleted
-     */
-    boolean deleteExtension(InputStream extensionStream) throws IOException;
-
-    /**
-     * Delete an extension by an id
-     *
-     * @param extensionId id of the extension
-     * @return true if the extension has been deleted
-     */
-    boolean deleteExtension(String extensionId);
-
-    /**
-     * Retrieves json schema extension metadatas, ordered according to the specified {@code sortBy} String and and paged: only {@code size}
-     * of them
-     * are retrieved, starting with the {@code
-     * offset}-th one.
-     *
-     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
-     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
-     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the
-     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
-     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
-     * @return a {@link PartialList} of json schema extension metadata
-     */
-    PartialList<Metadata> getJsonSchemaExtensionsMetadatas(int offset, int size, String sortBy);
-
-}
diff --git a/extensions/json-schema/pom.xml b/extensions/json-schema/pom.xml
new file mode 100644
index 0000000..dcc6e61
--- /dev/null
+++ b/extensions/json-schema/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-extensions</artifactId>
+        <version>2.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>unomi-json-schema-root</artifactId>
+    <name>Apache Unomi :: Extensions :: JSON Schema</name>
+    <description>JSON Schema extension for the Apache Unomi Context Server</description>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>services</module>
+        <module>rest</module>
+    </modules>
+
+</project>
diff --git a/extensions/json-schema/rest/pom.xml b/extensions/json-schema/rest/pom.xml
new file mode 100644
index 0000000..df4ed2b
--- /dev/null
+++ b/extensions/json-schema/rest/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-json-schema-root</artifactId>
+        <version>2.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>unomi-json-schema-rest</artifactId>
+    <name>Apache Unomi :: Extensions :: JSON Schema :: REST API</name>
+    <description>JSON Schema REST API for the Apache Unomi Context Server</description>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-json-schema-services</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-rest</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxws</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-rs-security-cors</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java b/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
similarity index 89%
rename from rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java
rename to extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
index dfc8900..26f0922 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java
+++ b/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.rest.endpoints;
+package org.apache.unomi.schema.rest;
 
 import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
 import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.services.SchemaService;
+import org.apache.unomi.rest.exception.InvalidRequestException;
+import org.apache.unomi.schema.api.SchemaService;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -27,15 +28,7 @@
 
 import javax.jws.WebMethod;
 import javax.jws.WebService;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Base64;
@@ -91,8 +84,12 @@
     @Consumes(MediaType.TEXT_PLAIN)
     @Produces(MediaType.APPLICATION_JSON)
     public Response save(String jsonSchema) {
-        schemaService.saveSchema(jsonSchema);
-        return Response.ok().build();
+        try {
+            schemaService.saveSchema(jsonSchema);
+            return Response.ok().build();
+        } catch (Exception e) {
+            throw new InvalidRequestException(e.getMessage(), "Unable to save schema");
+        }
     }
 
     /**
diff --git a/extensions/json-schema/services/pom.xml b/extensions/json-schema/services/pom.xml
new file mode 100644
index 0000000..86a54c8
--- /dev/null
+++ b/extensions/json-schema/services/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-json-schema-root</artifactId>
+        <version>2.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>unomi-json-schema-services</artifactId>
+    <name>Apache Unomi :: Extensions :: JSON Schema :: Service</name>
+    <description>JSON Schema service for the Apache Unomi Context Server</description>
+    <packaging>bundle</packaging>
+
+    <properties>
+        <version.schema>1.0.69</version.schema>
+        <version.schema.jackson>2.12.1</version.schema.jackson>
+        <version.schema.itu>1.5.1</version.schema.itu>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-persistence-spi</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <!-- dependencies required for json-schema framework -->
+        <dependency>
+            <groupId>com.networknt</groupId>
+            <artifactId>json-schema-validator</artifactId>
+            <version>${version.schema}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ethlo.time</groupId>
+            <artifactId>itu</artifactId>
+            <version>${version.schema.itu}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${version.schema.jackson}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${version.schema.jackson}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${version.schema.jackson}</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
+                        <Unomi-Source-Folders>${project.basedir}</Unomi-Source-Folders>
+                        <Export-Package>
+                            org.apache.unomi.schema.api
+                        </Export-Package>
+                        <Import-Package>
+                            sun.misc;resolution:=optional,
+                            org.jcodings;resolution:=optional,
+                            org.jcodings.specific;resolution:=optional,
+                            org.joni;resolution:=optional,
+                            org.joni.exception;resolution:=optional,
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-artifacts</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attach-artifact</goal>
+                        </goals>
+                        <configuration>
+                            <artifacts>
+                                <artifact>
+                                    <file>
+                                        src/main/resources/org.apache.unomi.schema.cfg
+                                    </file>
+                                    <type>cfg</type>
+                                    <classifier>schemacfg</classifier>
+                                </artifact>
+                            </artifacts>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
similarity index 74%
rename from api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java
rename to extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
index 3ae5077..ce12387 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
@@ -14,22 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema;
+
+package org.apache.unomi.schema.api;
 
 import org.apache.unomi.api.Metadata;
 import org.apache.unomi.api.MetadataItem;
 
 /**
- * Object which represents a JSON schema stored in the persistence service
+ * Object which represents a JSON schema, it's a wrapper because it contains some additional info used by the
+ * Service layer of Unomi like the id and the target.
+ * The JSON schema is store as String to avoid transformation during JSON schema resolution in the Unomi SchemaService.
+ * Also, it's extending  MetadataItem so that it can be persisted like that in Unomi storage system.
  */
-public class UnomiJSONSchema extends MetadataItem {
+public class JsonSchemaWrapper extends MetadataItem {
     public static final String ITEM_TYPE = "jsonSchema";
 
     private String id;
     private String schema;
     private String target;
 
-    public UnomiJSONSchema(){}
+    public JsonSchemaWrapper(){}
 
     /**
      * Instantiates a new JSON schema with an id and a schema as string
@@ -38,7 +42,7 @@
      * @param schema as string
      * @param target of the schema
      */
-    public UnomiJSONSchema(String id, String schema, String target) {
+    public JsonSchemaWrapper(String id, String schema, String target) {
         super(new Metadata(id));
         this.id = id;
         this.schema = schema;
@@ -68,4 +72,4 @@
     public void setTarget(String target) {
         this.target = target;
     }
-}
+}
\ No newline at end of file
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java
new file mode 100644
index 0000000..c479c40
--- /dev/null
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.schema.api;
+
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.PartialList;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Service that allow to manage JSON schema. It allows to get, save and delete schemas
+ */
+public interface SchemaService {
+
+    /**
+     * Retrieves json schema metadatas, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them
+     * are retrieved, starting with the {@code
+     * offset}-th one.
+     *
+     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
+     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the
+     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
+     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
+     * @return a {@link PartialList} of json schema metadata
+     */
+    PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy);
+
+    /**
+     * Verify if a jsonNode is valid against a schema
+     *
+     * @param data   to validate
+     * @param schemaId id of the schema used for the validation
+     * @return true is the object is valid
+     */
+    boolean isValid(String data, String schemaId);
+
+    /**
+     * Get a schema matching by a schema id
+     *
+     * @param schemaId Id of the schema
+     * @return A JSON schema
+     */
+    JsonSchemaWrapper getSchema(String schemaId);
+
+    /**
+     * Get a list a {@link JsonSchemaWrapper}
+     *
+     * @param target to filter the schemas
+     * @return a list of JSONSchema
+     */
+    List<JsonSchemaWrapper> getSchemasByTarget(String target);
+
+    /**
+     * Save a new schema or update a schema
+     *
+     * @param schema as a String value
+     */
+    void saveSchema(String schema);
+
+    /**
+     * Delete a schema according to its id
+     *
+     * @param schemaId id of the schema to delete
+     * @return true if the schema has been deleted
+     */
+    boolean deleteSchema(String schemaId);
+
+    /**
+     * Load a predefined schema into memory
+     *
+     * @param schemaStream inputStream of the schema
+     */
+    void loadPredefinedSchema(InputStream schemaStream) throws IOException;
+
+    /**
+     * Unload a predefined schema into memory
+     *
+     * @param schemaStream inputStream of the schema to delete
+     * @return true if the schema has been deleted
+     */
+    boolean unloadPredefinedSchema(InputStream schemaStream);
+}
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
new file mode 100644
index 0000000..e91d297
--- /dev/null
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.schema.impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.*;
+import com.networknt.schema.uri.URIFetcher;
+import org.apache.commons.io.IOUtils;
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.services.SchedulerService;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+public class SchemaServiceImpl implements SchemaService {
+
+    private static final String URI = "https://json-schema.org/draft/2019-09/schema";
+
+    private static final Logger logger = LoggerFactory.getLogger(SchemaServiceImpl.class.getName());
+
+    ObjectMapper objectMapper = new ObjectMapper();
+
+    private final Map<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new HashMap<>();
+    private Map<String, JsonSchemaWrapper> schemasById = new HashMap<>();
+
+    private Integer jsonSchemaRefreshInterval = 1000;
+    private ScheduledFuture<?> scheduledFuture;
+
+    private BundleContext bundleContext;
+    private PersistenceService persistenceService;
+    private SchedulerService schedulerService;
+    private JsonSchemaFactory jsonSchemaFactory;
+
+
+    @Override
+    public PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy) {
+        PartialList<JsonSchemaWrapper> items = persistenceService.getAllItems(JsonSchemaWrapper.class, offset, size, sortBy);
+        List<Metadata> details = new LinkedList<>();
+        for (JsonSchemaWrapper definition : items.getList()) {
+            details.add(definition.getMetadata());
+        }
+        return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize(), items.getTotalSizeRelation());
+    }
+
+    @Override
+    public boolean isValid(String data, String schemaId) {
+        JsonSchema jsonSchema = null;
+        JsonNode jsonNode = null;
+
+        try {
+            jsonNode = objectMapper.readTree(data);
+            jsonSchema = jsonSchemaFactory.getSchema(new URI(schemaId));
+        } catch (Exception e) {
+            logger.error("Failed to process data to validate because {} - Set SchemaServiceImpl at DEBUG level for more detail ", e.getMessage());
+            logger.debug("full error",e);
+            return false;
+        }
+
+        if (jsonNode == null) {
+            logger.warn("No data to validate");
+            return false;
+        }
+
+        if (jsonSchema == null) {
+            logger.warn("No schema found for {}", schemaId);
+            return false;
+        }
+
+        Set<ValidationMessage> validationMessages = jsonSchema.validate(jsonNode);
+        if (validationMessages == null || validationMessages.isEmpty()) {
+            return true;
+        }
+        for (ValidationMessage validationMessage : validationMessages) {
+            logger.error("Error validating object against schema {}: {}", schemaId, validationMessage);
+        }
+        return false;
+    }
+
+    @Override
+    public List<JsonSchemaWrapper> getSchemasByTarget(String target) {
+        return schemasById.values().stream().filter(jsonSchemaWrapper -> jsonSchemaWrapper.getTarget() != null && jsonSchemaWrapper.getTarget().equals(target))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void saveSchema(String schema) {
+        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
+        JsonNode schemaNode = jsonSchema.getSchemaNode();
+        String id = schemaNode.get("$id").asText();
+
+        if (!predefinedUnomiJSONSchemaById.containsKey(id)) {
+            String target = schemaNode.at("/self/target").asText();
+            String name = schemaNode.at("/self/name").asText();
+
+            if ("events".equals(target) && !name.matches("[_A-Za-z][_0-9A-Za-z]*")) {
+                throw new IllegalArgumentException(
+                        "The \"/self/name\" value should match the following regular expression [_A-Za-z][_0-9A-Za-z]* for the Json schema on events");
+            }
+
+            JsonSchemaWrapper jsonSchemaWrapper = new JsonSchemaWrapper(id, schema, target);
+            persistenceService.save(jsonSchemaWrapper);
+            schemasById.put(id, jsonSchemaWrapper);
+        } else {
+            throw new IllegalArgumentException("Trying to save a Json Schema that is using the ID of an existing Json Schema provided by Unomi is forbidden");
+        }
+    }
+
+    @Override
+    public boolean deleteSchema(String schemaId) {
+        // forbidden to delete predefined Unomi schemas
+        if (!predefinedUnomiJSONSchemaById.containsKey(schemaId)) {
+            schemasById.remove(schemaId);
+            return persistenceService.remove(schemaId, JsonSchemaWrapper.class);
+        }
+        return false;
+    }
+
+    @Override
+    public void loadPredefinedSchema(InputStream schemaStream) throws IOException {
+        String jsonSchema = IOUtils.toString(schemaStream);
+
+        // check that schema is valid and get the id
+        JsonNode schemaNode = jsonSchemaFactory.getSchema(jsonSchema).getSchemaNode();
+        String schemaId = schemaNode.get("$id").asText();
+        String target = schemaNode.at("/self/target").asText();
+        JsonSchemaWrapper jsonSchemaWrapper = new JsonSchemaWrapper(schemaId, jsonSchema, target);
+
+        predefinedUnomiJSONSchemaById.put(schemaId, jsonSchemaWrapper);
+        schemasById.put(schemaId, jsonSchemaWrapper);
+    }
+
+    @Override
+    public boolean unloadPredefinedSchema(InputStream schemaStream) {
+        JsonNode schemaNode = jsonSchemaFactory.getSchema(schemaStream).getSchemaNode();
+        String schemaId = schemaNode.get("$id").asText();
+
+        return predefinedUnomiJSONSchemaById.remove(schemaId) != null && schemasById.remove(schemaId) != null;
+    }
+
+    @Override
+    public JsonSchemaWrapper getSchema(String schemaId) {
+        return schemasById.get(schemaId);
+    }
+
+    private URIFetcher getUriFetcher() {
+        return uri -> {
+            logger.debug("Fetching schema {}", uri);
+            JsonSchemaWrapper jsonSchemaWrapper = schemasById.get(uri.toString());
+            if (jsonSchemaWrapper == null) {
+                logger.error("Couldn't find schema {}", uri);
+                return null;
+            }
+            return IOUtils.toInputStream(jsonSchemaWrapper.getSchema());
+        };
+    }
+
+    private void refreshJSONSchemas() {
+        schemasById = new HashMap<>();
+        schemasById.putAll(predefinedUnomiJSONSchemaById);
+
+        persistenceService.getAllItems(JsonSchemaWrapper.class).forEach(
+                JsonSchemaWrapper -> schemasById.put(JsonSchemaWrapper.getId(), JsonSchemaWrapper));
+    }
+
+    private void initializeTimers() {
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                refreshJSONSchemas();
+            }
+        };
+        scheduledFuture = schedulerService.getScheduleExecutorService()
+                .scheduleWithFixedDelay(task, 0, jsonSchemaRefreshInterval, TimeUnit.MILLISECONDS);
+    }
+
+    public void init() {
+        JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
+                .addKeyword(new NonValidationKeyword("self"))
+                .build();
+
+        jsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
+                .addMetaSchema(jsonMetaSchema)
+                .defaultMetaSchemaURI(URI)
+                .uriFetcher(getUriFetcher(), "https", "http")
+                .build();
+
+        initializeTimers();
+        logger.info("Schema service initialized.");
+    }
+
+    public void destroy() {
+        scheduledFuture.cancel(true);
+        logger.info("Schema service shutdown.");
+    }
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setSchedulerService(SchedulerService schedulerService) {
+        this.schedulerService = schedulerService;
+    }
+
+    public void setJsonSchemaRefreshInterval(Integer jsonSchemaRefreshInterval) {
+        this.jsonSchemaRefreshInterval = jsonSchemaRefreshInterval;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+}
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java
new file mode 100644
index 0000000..9e674d5
--- /dev/null
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.schema.listener;
+
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * An implementation of a BundleListener for the JSON schema.
+ * It will load the pre-defined schema files in the folder META-INF/cxs/schemas.
+ * It will load the extension of schema in the folder META-INF/cxs/schemasextensions.
+ * The scripts will be stored in the ES index jsonSchema and the extension will be stored in jsonSchemaExtension
+ */
+public class JsonSchemaListener implements SynchronousBundleListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(JsonSchemaListener.class.getName());
+    public static final String ENTRIES_LOCATION = "META-INF/cxs/schemas";
+
+    private PersistenceService persistenceService;
+    private SchemaService schemaService;
+    private BundleContext bundleContext;
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setSchemaService(SchemaService schemaService) {
+        this.schemaService = schemaService;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void postConstruct() {
+        logger.info("JSON schema listener initializing...");
+        logger.debug("postConstruct {}", bundleContext.getBundle());
+        createIndexes();
+
+        loadPredefinedSchemas(bundleContext, true);
+
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null && bundle.getBundleId() != bundleContext.getBundle().getBundleId()) {
+                loadPredefinedSchemas(bundle.getBundleContext(), true);
+            }
+        }
+
+        bundleContext.addBundleListener(this);
+        logger.info("JSON schema listener initialized.");
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+        logger.info("JSON schema listener shutdown.");
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        loadPredefinedSchemas(bundleContext, true);
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        loadPredefinedSchemas(bundleContext, false);
+    }
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                if (!event.getBundle().getSymbolicName().equals(bundleContext.getBundle().getSymbolicName())) {
+                    processBundleStop(event.getBundle().getBundleContext());
+                }
+                break;
+        }
+    }
+
+    public void createIndexes() {
+        if (persistenceService.createIndex(JsonSchemaWrapper.ITEM_TYPE)) {
+            logger.info("{} index created", JsonSchemaWrapper.ITEM_TYPE);
+        } else {
+            logger.info("{} index already exists", JsonSchemaWrapper.ITEM_TYPE);
+        }
+    }
+
+    private void loadPredefinedSchemas(BundleContext bundleContext, boolean load) {
+        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
+        if (predefinedSchemas == null) {
+            return;
+        }
+
+        while (predefinedSchemas.hasMoreElements()) {
+            URL predefinedSchemaURL = predefinedSchemas.nextElement();
+            logger.debug("Found predefined JSON schema at {}, {}... ", predefinedSchemaURL, load ? "loading" : "unloading");
+            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
+                if (load) {
+                    schemaService.loadPredefinedSchema(schemaInputStream);
+                } else {
+                    schemaService.unloadPredefinedSchema(schemaInputStream);
+                }
+            } catch (Exception e) {
+                logger.error("Error while {} schema definition {}", load ? "loading" : "unloading", predefinedSchemaURL, e);
+            }
+        }
+    }
+}
diff --git a/services/src/main/resources/META-INF/cxs/schemas/condition.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/condition.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/condition.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/condition.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/conditiontype.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/consent.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/consent.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/consent.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/consent.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/consentType.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/consentType.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/consentType.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/consentType.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/customitem.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitem.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/customitem.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitem.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/customitems/page.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/customitems/site.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/event.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/event.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/event.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/event.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/anonymizeProfile.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/anonymizeProfile.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/anonymizeProfile.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/anonymizeProfile.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/articleCompleted.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/articleCompleted.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/articleCompleted.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/articleCompleted.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/form.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/form.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/form.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/form.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/goal.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/goal.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/goal.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/goal.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/identify.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/identify.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/identify.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/identify.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/incrementInterest.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/incrementInterest.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/incrementInterest.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/incrementInterest.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/login.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/login.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/login.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/login.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/profileDeleted.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/profileDeleted.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/profileDeleted.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/profileDeleted.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/profileUpdated.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/profileUpdated.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/profileUpdated.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/profileUpdated.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/ruleFired.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/ruleFired.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/ruleFired.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/ruleFired.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/search.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/search.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/search.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/search.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/sessionCreated.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/sessionCreated.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/sessionCreated.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/sessionCreated.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/sessionReassigned.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/sessionReassigned.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/sessionReassigned.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/sessionReassigned.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/updateProperties.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/updateProperties.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/updateProperties.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/updateProperties.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/view.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/view.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/events/view.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/view.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/goal.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/goal.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/goal.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/goal.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/item.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/item.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/item.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/item.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/metadata.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/metadata.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/metadata.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/metadata.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/metadataitem.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/parameter.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/parameter.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/parameter.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/parameter.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/profile.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/profile.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/profile.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/profile.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/session.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/session.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/session.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/session.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
similarity index 84%
rename from services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
index b4e1528..b1e8156 100644
--- a/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
+++ b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
@@ -6,8 +6,7 @@
   "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" }],
   "properties" : {
     "timeStamp" : {
-      "type" : ["string"],
-      "format" : "date-time"
+      "type" :  ["string", "integer"]
     }
   }
 }
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/boolean.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/date.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/date.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/date.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/date.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/email.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/email.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/email.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/email.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/integer.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/integer.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/integer.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/integer.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/long.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/long.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/long.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/long.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/set.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/set.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/set.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/set.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/string.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/string.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/values/string.json
rename to extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/values/string.json
diff --git a/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..02e3280
--- /dev/null
+++ b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
+           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
+
+    <cm:property-placeholder persistent-id="org.apache.unomi.schema" update-strategy="reload">
+        <cm:default-properties>
+            <cm:property name="json.schema.refresh.interval" value="1000"/>
+        </cm:default-properties>
+    </cm:property-placeholder>
+
+    <reference id="profileService" interface="org.apache.unomi.api.services.ProfileService"/>
+    <reference id="persistenceService" interface="org.apache.unomi.persistence.spi.PersistenceService"/>
+    <reference id="schedulerService" interface="org.apache.unomi.api.services.SchedulerService"/>
+
+    <bean id="schemaServiceImpl" class="org.apache.unomi.schema.impl.SchemaServiceImpl" init-method="init"
+          destroy-method="destroy">
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="schedulerService" ref="schedulerService"/>
+        <property name="jsonSchemaRefreshInterval" value="${json.schema.refresh.interval}"/>
+    </bean>
+    <service id="schemaService" ref="schemaServiceImpl" interface="org.apache.unomi.schema.api.SchemaService"/>
+
+    <bean id="jsonSchemaListenerImpl" class="org.apache.unomi.schema.listener.JsonSchemaListener"
+          init-method="postConstruct" destroy-method="preDestroy">
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="schemaService" ref="schemaServiceImpl"/>
+    </bean>
+    <service id="jsonSchemaListener" ref="jsonSchemaListenerImpl">
+        <interfaces>
+            <value>org.osgi.framework.SynchronousBundleListener</value>
+        </interfaces>
+    </service>
+</blueprint>
diff --git a/extensions/json-schema/services/src/main/resources/org.apache.unomi.schema.cfg b/extensions/json-schema/services/src/main/resources/org.apache.unomi.schema.cfg
new file mode 100644
index 0000000..b930b86
--- /dev/null
+++ b/extensions/json-schema/services/src/main/resources/org.apache.unomi.schema.cfg
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The interval in milliseconds to reload the json schemas in memory
+services.json.schema.refresh.interval=${org.apache.unomi.json.schema.refresh.interval:-1000}
\ No newline at end of file
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 70105e5..5b7c58d 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -40,6 +40,7 @@
         <module>weather-update</module>
         <module>web-tracker</module>
         <module>groovy-actions</module>
+        <module>json-schema</module>
     </modules>
 
 </project>
diff --git a/graphql/cxs-impl/pom.xml b/graphql/cxs-impl/pom.xml
index d79a7ce..3ff2923 100644
--- a/graphql/cxs-impl/pom.xml
+++ b/graphql/cxs-impl/pom.xml
@@ -149,6 +149,12 @@
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-json-schema-services</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
index eff1964..84b9ec8 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
@@ -16,6 +16,8 @@
  */
 package org.apache.unomi.graphql.schema;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.networknt.schema.JsonSchema;
 import graphql.annotations.AnnotationsSchemaCreator;
 import graphql.annotations.processor.GraphQLAnnotations;
 import graphql.annotations.processor.ProcessingElementsContainer;
@@ -34,11 +36,10 @@
 import graphql.schema.GraphQLType;
 import graphql.schema.visibility.GraphqlFieldVisibility;
 import org.apache.unomi.api.PropertyType;
-import org.apache.unomi.api.schema.json.JSONObjectType;
-import org.apache.unomi.api.schema.json.JSONSchema;
-import org.apache.unomi.api.schema.json.JSONType;
+import org.apache.unomi.graphql.schema.json.JSONObjectType;
+import org.apache.unomi.graphql.schema.json.JSONSchema;
+import org.apache.unomi.graphql.schema.json.JSONType;
 import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.graphql.CDPGraphQLConstants;
 import org.apache.unomi.graphql.converters.UnomiToGraphQLConverter;
 import org.apache.unomi.graphql.fetchers.CustomEventOrSetPropertyDataFetcher;
@@ -55,6 +56,7 @@
 import org.apache.unomi.graphql.providers.GraphQLQueryProvider;
 import org.apache.unomi.graphql.providers.GraphQLSubscriptionProvider;
 import org.apache.unomi.graphql.providers.GraphQLTypeFunctionProvider;
+import org.apache.unomi.graphql.schema.json.JSONTypeFactory;
 import org.apache.unomi.graphql.types.input.CDPEventFilterInput;
 import org.apache.unomi.graphql.types.input.CDPEventInput;
 import org.apache.unomi.graphql.types.input.CDPEventProcessor;
@@ -69,8 +71,11 @@
 import org.apache.unomi.graphql.types.output.CDPProfile;
 import org.apache.unomi.graphql.types.output.RootMutation;
 import org.apache.unomi.graphql.types.output.RootQuery;
+import org.apache.unomi.graphql.utils.GraphQLObjectMapper;
 import org.apache.unomi.graphql.utils.ReflectionUtil;
 import org.apache.unomi.graphql.utils.StringUtils;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -331,7 +336,8 @@
     }
 
     private void registerDynamicUnomiInputEvents(GraphQLSchema.Builder schemaBuilder) {
-        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events");
+        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream()
+                .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList());
 
         if (!unomiEventTypes.isEmpty()) {
             for (JSONSchema unomiEventType : unomiEventTypes) {
@@ -353,7 +359,8 @@
     }
 
     private void registerDynamicUnomiOutputEvents(GraphQLSchema.Builder schemaBuilder) {
-        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events");
+        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream()
+                .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList());
 
         if (!unomiEventTypes.isEmpty()) {
             final GraphQLCodeRegistry.Builder codeRegisterBuilder = graphQLAnnotations.getContainer().getCodeRegistryBuilder();
@@ -650,7 +657,8 @@
         }
 
         // now add all unomi defined event types
-        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events");
+        final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream()
+                .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList());
         unomiEventTypes.forEach(eventType -> {
             final String typeName = UnomiToGraphQLConverter.convertEventType(eventType.getName());
             final GraphQLInputType eventInputType = (GraphQLInputType) getFromTypeRegistry(typeName + "Input");
@@ -668,6 +676,17 @@
         registerInTypeRegistry(CDPEventInput.TYPE_NAME, builder.build());
     }
 
+    public static JSONSchema buildJSONSchema(JsonSchemaWrapper jsonSchemaWrapper, SchemaService schemaService) {
+        Map<String, Object> schemaMap;
+        try {
+            schemaMap = GraphQLObjectMapper.getInstance().readValue(jsonSchemaWrapper.getSchema(), Map.class);
+        } catch (JsonProcessingException e) {
+            logger.error("Failed to process Json object, e");
+            schemaMap = Collections.emptyMap();
+        }
+        return new JSONSchema(schemaMap, new JSONTypeFactory(schemaService));
+    }
+
     private void registerDynamicEventFilterInputFields() {
         final List<GraphQLInputObjectField> fieldDefinitions = new ArrayList<>();
 
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaUpdater.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaUpdater.java
index 7d6198d..37cd40a 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaUpdater.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaUpdater.java
@@ -21,7 +21,6 @@
 import graphql.schema.GraphQLCodeRegistry;
 import graphql.schema.GraphQLSchema;
 import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.graphql.fetchers.event.UnomiEventPublisher;
 import org.apache.unomi.graphql.providers.GraphQLAdditionalTypesProvider;
 import org.apache.unomi.graphql.providers.GraphQLCodeRegistryProvider;
@@ -37,6 +36,7 @@
 import org.apache.unomi.graphql.types.output.CDPProfile;
 import org.apache.unomi.graphql.types.output.CDPProfileInterface;
 import org.apache.unomi.graphql.types.output.CDPPropertyInterface;
+import org.apache.unomi.schema.api.SchemaService;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONArrayType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONArrayType.java
index aa8cc2a..427b31e 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONArrayType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.List;
 import java.util.Map;
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONBooleanType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONBooleanType.java
index aea431e..79f541e 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONBooleanType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONEnumType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONEnumType.java
index 57ba1e6..6cee489 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONEnumType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONIntegerType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONIntegerType.java
index 9ae40dd..e873d12 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONIntegerType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNullType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNullType.java
index 809a29f..3cdd3ce 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNullType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNumberType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNumberType.java
index d591777..6233217 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONNumberType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONObjectType.java
similarity index 97%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONObjectType.java
index da28f41..be91e0a 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONObjectType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.HashMap;
 import java.util.List;
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONSchema.java
similarity index 97%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONSchema.java
index 1e65f8e..fe159b0 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONSchema.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import org.apache.unomi.api.PluginType;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONStringType.java
similarity index 95%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONStringType.java
index c63f6b5..238c618 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONStringType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.Map;
 
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONType.java
similarity index 97%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONType.java
index d1c0474..2f8afa2 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONType.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONTypeFactory.java
similarity index 89%
rename from api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java
rename to graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONTypeFactory.java
index 33f4b72..6b1ab82 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/json/JSONTypeFactory.java
@@ -14,9 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.api.schema.json;
+package org.apache.unomi.graphql.schema.json;
 
-import org.apache.unomi.api.services.SchemaService;
+import org.apache.unomi.graphql.schema.GraphQLSchemaProvider;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,9 +51,9 @@
     List<JSONType> getTypes(Map<String, Object> schemaTree) {
         if (schemaTree.containsKey("$ref")) {
             String schemaId = (String) schemaTree.get("$ref");
-            JSONSchema refSchema = schemaService.getSchema(schemaId);
+            JsonSchemaWrapper refSchema = schemaService.getSchema(schemaId);
             if (refSchema != null) {
-                schemaTree = refSchema.getSchemaTree();
+                schemaTree = GraphQLSchemaProvider.buildJSONSchema(refSchema, schemaService).getSchemaTree();
             } else {
                 System.err.println("Couldn't find schema for ref " + schemaId);
             }
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/resolvers/CDPEventInterfaceResolver.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/resolvers/CDPEventInterfaceResolver.java
index 2d6cebb..1ded244 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/resolvers/CDPEventInterfaceResolver.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/resolvers/CDPEventInterfaceResolver.java
@@ -18,11 +18,13 @@
 
 import graphql.TypeResolutionEnvironment;
 import graphql.schema.GraphQLObjectType;
-import org.apache.unomi.api.schema.json.JSONSchema;
-import org.apache.unomi.api.services.SchemaService;
+import org.apache.unomi.graphql.schema.GraphQLSchemaProvider;
+import org.apache.unomi.graphql.schema.json.JSONSchema;
 import org.apache.unomi.graphql.converters.UnomiToGraphQLConverter;
 import org.apache.unomi.graphql.services.ServiceManager;
 import org.apache.unomi.graphql.types.output.CDPEventInterface;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
 
 public class CDPEventInterfaceResolver extends BaseTypeResolver {
 
@@ -32,11 +34,10 @@
         SchemaService schemaService = serviceManager.getService(SchemaService.class);
 
         final CDPEventInterface eventInterface = env.getObject();
-        final JSONSchema eventSchema =
-                schemaService.getSchema("https://unomi.apache.org/schemas/json/events/" + eventInterface.getEvent().getEventType() + "/1-0"
-                        + "-0");
+        final JsonSchemaWrapper eventSchema = schemaService.getSchema("https://unomi.apache.org/schemas/json/events/" + eventInterface.getEvent().getEventType() + "/1-0-0");
         if (eventSchema != null) {
-            final String typeName = UnomiToGraphQLConverter.convertEventType(eventSchema.getName());
+            final JSONSchema jsonSchema = GraphQLSchemaProvider.buildJSONSchema(eventSchema, schemaService);
+            final String typeName = UnomiToGraphQLConverter.convertEventType(jsonSchema.getName());
             return env.getSchema().getObjectType(typeName);
         } else {
             return super.getType(env);
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 9abf854..cece1ce 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -17,6 +17,7 @@
 
 package org.apache.unomi.itests;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.auth.AuthScope;
@@ -113,6 +114,13 @@
     protected static final int DEFAULT_TRYING_TRIES = 30;
     private final static String JSONSCHEMA_URL = "/cxs/jsonSchema";
 
+    protected final static ObjectMapper objectMapper;
+
+    static {
+        objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+    }
+
     @Inject
     @Filter(timeout = 600000)
     protected PersistenceService persistenceService;
@@ -448,7 +456,6 @@
             final HttpGet httpGet = new HttpGet(getFullUrl(url));
             response = executeHttpRequest(httpGet);
             if (response.getStatusLine().getStatusCode() == 200) {
-                ObjectMapper objectMapper = new ObjectMapper();
                 return objectMapper.readValue(response.getEntity().getContent(), clazz);
             } else {
                 return null;
@@ -528,7 +535,6 @@
     protected String resourceAsString(final String resource) {
         final java.net.URL url = bundleContext.getBundle().getResource(resource);
         try (InputStream stream = url.openStream()) {
-            final ObjectMapper objectMapper = new ObjectMapper();
             return objectMapper.writeValueAsString(objectMapper.readTree(stream));
         } catch (final Exception e) {
             throw new RuntimeException(e);
diff --git a/itests/src/test/java/org/apache/unomi/itests/BasicIT.java b/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
index 348bdc3..b90c0b0 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
@@ -17,7 +17,9 @@
 
 package org.apache.unomi.itests;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -59,7 +61,6 @@
 public class BasicIT extends BaseIT {
     private final static Logger LOGGER = LoggerFactory.getLogger(BasicIT.class);
 
-    private final ObjectMapper objectMapper = new ObjectMapper();
 
     private static final String SESSION_ID_0 = "aa3b04bd-8f4d-4a07-8e96-d33ffa04d3d0";
     private static final String SESSION_ID_1 = "aa3b04bd-8f4d-4a07-8e96-d33ffa04d3d1";
diff --git a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
index e21608d..4e1b759 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
@@ -91,8 +91,6 @@
     private static final int DEFAULT_TRYING_TIMEOUT = 2000;
     private static final int DEFAULT_TRYING_TRIES = 30;
 
-    private ObjectMapper objectMapper = new ObjectMapper();
-
     @Inject
     @Filter(timeout = 600000)
     protected EventService eventService;
diff --git a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
index 5b14aa7..97879bd 100644
--- a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
@@ -19,10 +19,9 @@
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.entity.ContentType;
-import org.apache.unomi.api.schema.UnomiJSONSchema;
-import org.apache.unomi.api.schema.JSONSchemaExtension;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.schema.api.JsonSchemaWrapper;
+import org.apache.unomi.schema.api.SchemaService;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,7 +49,6 @@
 @ExamReactorStrategy(PerSuite.class)
 public class JSONSchemaIT extends BaseIT {
     private final static String JSONSCHEMA_URL = "/cxs/jsonSchema";
-    private final static String JSONSCHEMAEXTENSION_URL = "/cxs/jsonSchemaExtension";
     private static final int DEFAULT_TRYING_TIMEOUT = 2000;
     private static final int DEFAULT_TRYING_TRIES = 30;
 
@@ -66,14 +64,11 @@
     public void setUp() throws InterruptedException {
         keepTrying("Couldn't find json schema endpoint", () -> get(JSONSCHEMA_URL, List.class), Objects::nonNull, DEFAULT_TRYING_TIMEOUT,
                 DEFAULT_TRYING_TRIES);
-        keepTrying("Couldn't find json schema extension endpoint", () -> get(JSONSCHEMAEXTENSION_URL, List.class), Objects::nonNull,
-                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
     }
 
     @After
     public void tearDown() {
         schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0");
-        schemaService.deleteExtension("extension-test-event-1");
     }
 
     @Test
@@ -92,7 +87,7 @@
     @Test
     public void testSaveNewValidJSONSchema() throws InterruptedException {
 
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
 
         CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
 
@@ -103,8 +98,16 @@
     }
 
     @Test
+    public void testSavePredefinedJSONSchema() throws IOException {
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
+        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/predefined-event-type.json", ContentType.TEXT_PLAIN)) {
+            assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
     public void testDeleteJSONSchema() throws InterruptedException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
 
         post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
 
@@ -124,60 +127,17 @@
 
     @Test
     public void testSaveNewInvalidJSONSchema() throws IOException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
         try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-invalid.json", ContentType.TEXT_PLAIN)) {
-            assertEquals("Save should have failed", 500, response.getStatusLine().getStatusCode());
+            assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
         }
     }
 
     @Test
     public void testSaveSchemaWithInvalidName() throws IOException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
         try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-invalid-name.json", ContentType.TEXT_PLAIN)) {
-            assertEquals("Save should have failed", 500, response.getStatusLine().getStatusCode());
+            assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
         }
     }
-    @Test
-    public void testGetJsonSchemaExtensionsMetadatas() throws InterruptedException {
-        List jsonSchemaExtensions = get(JSONSCHEMAEXTENSION_URL, List.class);
-        assertTrue("JSON schema extension list should be empty", jsonSchemaExtensions.isEmpty());
-
-        post(JSONSCHEMAEXTENSION_URL, "schemas/extension/extension-test-event-example.json", ContentType.TEXT_PLAIN);
-
-        jsonSchemaExtensions = keepTrying("Couldn't find json extensions", () -> get(JSONSCHEMAEXTENSION_URL, List.class),
-                (list) -> !list.isEmpty(), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
-        assertFalse("JSON schema list should not be empty", jsonSchemaExtensions.isEmpty());
-        assertEquals("JSON schema list should not be empty", 1, jsonSchemaExtensions.size());
-    }
-
-    @Test
-    public void testSaveNewJSONSchemaExtension() throws InterruptedException {
-
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JSONSchemaExtension.class).isEmpty());
-
-        CloseableHttpResponse response = post(JSONSCHEMAEXTENSION_URL, "schemas/extension/extension-test-event-example.json",
-                ContentType.TEXT_PLAIN);
-
-        assertEquals("Invalid response code", 200, response.getStatusLine().getStatusCode());
-
-        keepTrying("Couldn't find json schemas extensions", () -> get(JSONSCHEMAEXTENSION_URL, List.class), (list) -> !list.isEmpty(),
-                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
-    }
-
-    @Test
-    public void testDeleteJSONSchemaExtension() throws InterruptedException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JSONSchemaExtension.class).isEmpty());
-
-        post(JSONSCHEMAEXTENSION_URL, "schemas/extension/extension-test-event-example.json", ContentType.TEXT_PLAIN);
-
-        keepTrying("Couldn't find json schemas extension", () -> get(JSONSCHEMAEXTENSION_URL, List.class), (list) -> !list.isEmpty(),
-                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
-
-        CloseableHttpResponse response = delete(JSONSCHEMAEXTENSION_URL + "/extension-test-event-1");
-        assertEquals("Invalid response code", 204, response.getStatusLine().getStatusCode());
-
-        keepTrying("wait for empty list of schemas extensions", () -> get(JSONSCHEMAEXTENSION_URL, List.class), List::isEmpty,
-                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
-    }
-
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
index 0b4073e..2767a38 100644
--- a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
+++ b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
@@ -62,7 +62,6 @@
 			return null;
 		}
 		String jsonFromResponse = EntityUtils.toString(response.getEntity());
-		// ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
 		ObjectMapper mapper = CustomObjectMapper.getObjectMapper();
 		try {
 			T value = mapper.readValue(jsonFromResponse, clazz);
diff --git a/itests/src/test/resources/personalization-controlgroup.json b/itests/src/test/resources/personalization-controlgroup.json
index 8b8c74f..c4a1bcf 100644
--- a/itests/src/test/resources/personalization-controlgroup.json
+++ b/itests/src/test/resources/personalization-controlgroup.json
@@ -3,7 +3,6 @@
     "itemId": "CMSServer",
     "itemType": "custom",
     "scope": "acme",
-    "version": null,
     "properties": {}
   },
   "requireSegments": true,
@@ -13,8 +12,6 @@
   "requiredSessionProperties": [
     "unomiControlGroups"
   ],
-  "events": null,
-  "filters": null,
   "personalizations": [
     {
       "id": "perso1",
@@ -33,7 +30,6 @@
           "id": "perso1content1",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -84,7 +80,6 @@
           "id": "perso1content2",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -126,14 +121,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content3",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -175,14 +168,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content4",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -224,14 +215,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content5",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -273,14 +262,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content6",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -322,14 +309,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content7",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -371,14 +356,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content8",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -420,14 +403,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content9",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -469,14 +450,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content10",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -518,8 +497,7 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         }
       ]
     },
@@ -534,7 +512,6 @@
           "id": "perso2content1",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -585,7 +562,6 @@
           "id": "perso2content2",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -627,14 +603,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso1content3",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -676,14 +650,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content4",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -725,14 +697,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content5",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -774,14 +744,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content6",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -823,14 +791,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content7",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -872,14 +838,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content8",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -921,14 +885,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content9",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -970,14 +932,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "perso2content10",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -1019,13 +979,10 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         }
       ]
     }
   ],
-  "profileOverrides": null,
-  "sessionPropertiesOverrides": null,
   "sessionId": "test-session-id"
 }
\ No newline at end of file
diff --git a/itests/src/test/resources/personalization.json b/itests/src/test/resources/personalization.json
index 89ac156..652d50a 100644
--- a/itests/src/test/resources/personalization.json
+++ b/itests/src/test/resources/personalization.json
@@ -3,7 +3,6 @@
     "itemId": "CMSServer",
     "itemType": "custom",
     "scope": "acme",
-    "version": null,
     "properties": {}
   },
   "requireSegments": true,
@@ -13,8 +12,6 @@
   "requiredSessionProperties": [
     "*"
   ],
-  "events": null,
-  "filters": null,
   "personalizations": [
     {
       "id": "recommendations-by-interest",
@@ -27,7 +24,6 @@
           "id": "ac02dbe3-2445-4727-acea-50cfed001996",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -35,7 +31,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -78,7 +74,6 @@
           "id": "62194fdd-2a1c-46ce-8f9b-19dc168f3454",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -86,7 +81,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -120,14 +115,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "dbb3a797-d24e-4701-97ad-dc0a708fa06f",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -135,7 +128,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -169,14 +162,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "90664d1d-6558-4239-a024-8212f2652673",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -184,7 +175,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -218,14 +209,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "f13af275-2002-4200-bd58-96847c74dafb",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -233,7 +222,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -267,14 +256,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "2305ab1a-f6b1-47c1-bc46-a8c69c553d87",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -282,7 +269,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -316,14 +303,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "50c4064c-ef6e-4afe-adcf-f876111f50bc",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -331,7 +316,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -365,14 +350,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "55371bb0-c516-4038-ab6d-0acab57b4790",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -380,7 +363,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -414,14 +397,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "eaaa15eb-e2c2-49cf-bb99-20ea90bd3fdb",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -429,7 +410,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -463,14 +444,12 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         },
         {
           "id": "15cdd5dd-909d-4d91-8f79-62226b96843a",
           "filters": [
             {
-              "appliesOn": null,
               "condition": {
                 "parameterValues": {
                   "minimumEventCount": 1,
@@ -478,7 +457,7 @@
                     "type": "booleanCondition",
                     "parameterValues": {
                       "operator": "and",
-                      "subConditions" : [
+                      "subConditions": [
                         {
                           "type": "eventTypeCondition",
                           "parameterValues": {
@@ -512,13 +491,10 @@
                 "score": -1000
               }
             }
-          ],
-          "properties": null
+          ]
         }
       ]
     }
   ],
-  "profileOverrides": null,
-  "sessionPropertiesOverrides": null,
   "sessionId": "21bf6d3d-ba5d-4b8c-98bb-af4dcf506c03"
 }
\ No newline at end of file
diff --git a/itests/src/test/resources/schemas/events/predefined-event-type.json b/itests/src/test/resources/schemas/events/predefined-event-type.json
new file mode 100644
index 0000000..8d9ffd9
--- /dev/null
+++ b/itests/src/test/resources/schemas/events/predefined-event-type.json
@@ -0,0 +1,14 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/event/1-0-0",
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "self":{
+    "vendor":"org.apache.unomi",
+    "name":"testEventType",
+    "format":"jsonschema",
+    "target":"events",
+    "version":"1-0-0"
+  },
+  "title": "TestEvent",
+  "type": "object",
+  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
+}
diff --git a/itests/src/test/resources/schemas/extension/extension-test-event-example.json b/itests/src/test/resources/schemas/extension/extension-test-event-example.json
deleted file mode 100644
index e03fecc..0000000
--- a/itests/src/test/resources/schemas/extension/extension-test-event-example.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "id": "extension-test-event-1",
-  "schemaId": "https://unomi.apache.org/schemas/json/events/test-event-type/1-0-0",
-  "description": "An event for example",
-  "name": "Name of the event",
-  "priority": 10,
-  "extension": {
-    "allOf": [
-      {
-        "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0"
-      }
-    ],
-    "properties": {
-      "properties": {
-        "type": "object",
-        "properties": {
-          "floatProperty": {
-            "type": "number",
-            "maximum": "100",
-            "description": "Extension of float property"
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/kar/pom.xml b/kar/pom.xml
index f25e555..23824b2 100644
--- a/kar/pom.xml
+++ b/kar/pom.xml
@@ -116,6 +116,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-json-schema-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-groovy-actions-services</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/kar/src/main/feature/feature.xml b/kar/src/main/feature/feature.xml
index 3b0d695..7040610 100644
--- a/kar/src/main/feature/feature.xml
+++ b/kar/src/main/feature/feature.xml
@@ -45,6 +45,7 @@
         <configfile finalname="/etc/hazelcast.xml">mvn:org.apache.unomi/unomi-services/${project.version}/xml/hazelcastconfig</configfile>
         <configfile finalname="/etc/org.apache.unomi.geonames.cfg">mvn:org.apache.unomi/cxs-geonames-services/${project.version}/cfg/geonamescfg</configfile>
         <configfile finalname="/etc/org.apache.unomi.groovy.actions.cfg">mvn:org.apache.unomi/unomi-groovy-actions-services/${project.version}/cfg/groovyactionscfg</configfile>
+        <configfile finalname="/etc/org.apache.unomi.schema.cfg">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}/cfg/schemacfg</configfile>
         <bundle start-level="75">mvn:commons-io/commons-io/2.4</bundle>
         <bundle start-level="75">mvn:com.fasterxml.jackson.core/jackson-core/${version.jackson.core}</bundle>
         <bundle start-level="75">mvn:com.github.fge/btf/1.2</bundle>
@@ -72,6 +73,8 @@
         <bundle start-level="75" start="false">mvn:org.apache.unomi/unomi-persistence-spi/${project.version}</bundle>
         <bundle start-level="76" start="false">mvn:org.apache.unomi/unomi-persistence-elasticsearch-core/${project.version}</bundle>
         <bundle start-level="77" start="false">mvn:org.apache.unomi/unomi-services/${project.version}</bundle>
+        <bundle start-level="77" start="false">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}</bundle>
+        <bundle start-level="77" start="false">mvn:org.apache.unomi/unomi-json-schema-rest/${project.version}</bundle>
         <bundle start-level="77" start="false">mvn:org.apache.unomi/cxs-lists-extension-services/${project.version}</bundle>
         <bundle start-level="77" start="false">mvn:org.apache.unomi/cxs-lists-extension-rest/${project.version}</bundle>
         <bundle start-level="77" start="false">mvn:org.apache.unomi/cxs-geonames-services/${project.version}</bundle>
diff --git a/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 050a2ae..4eb80ac 100644
--- a/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -49,6 +49,8 @@
                 <entry key="org.apache.unomi.cxs-geonames-rest" value="false"/>
                 <entry key="org.apache.unomi.cxs-privacy-extension-services" value="false"/>
                 <entry key="org.apache.unomi.cxs-privacy-extension-rest" value="false"/>
+                <entry key="org.apache.unomi.json-schema-services" value="false"/>
+                <entry key="org.apache.unomi.json-schema-rest" value="false"/>
                 <entry key="org.apache.unomi.rest" value="false"/>
                 <entry key="org.apache.unomi.wab" value="false"/>
                 <entry key="org.apache.unomi.plugins-base" value="false"/>
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
index 9e23a23..1d14d55 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
@@ -17,6 +17,7 @@
 
 package org.apache.unomi.persistence.spi;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.Version;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
@@ -58,6 +59,7 @@
         super();
         super.registerModule(new JaxbAnnotationModule());
         configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+        setSerializationInclusion(JsonInclude.Include.NON_NULL);
         ISO8601DateFormat dateFormat = new ISO8601DateFormat();
         dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
         setDateFormat(dateFormat);
diff --git a/rest/pom.xml b/rest/pom.xml
index 77ae8fc..6631e9f 100644
--- a/rest/pom.xml
+++ b/rest/pom.xml
@@ -44,6 +44,12 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-json-schema-services</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
             <scope>provided</scope>
diff --git a/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
index ad33945..ba9d9aa 100644
--- a/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
+++ b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
@@ -27,8 +27,8 @@
 import org.apache.unomi.api.Item;
 import org.apache.unomi.api.Profile;
 import org.apache.unomi.api.services.PersonalizationService;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.rest.exception.InvalidRequestException;
+import org.apache.unomi.schema.api.SchemaService;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -55,7 +55,7 @@
     public ContextRequest deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
         JsonNode node = jsonParser.getCodec().readTree(jsonParser);
         // Validate schema on it
-        if (!schemaService.isValid(node, "https://unomi.apache.org/schemas/json/contextrequest/1-0-0")) {
+        if (!schemaService.isValid(node.toString(), "https://unomi.apache.org/schemas/json/contextrequest/1-0-0")) {
             throw new InvalidRequestException("Invalid Context request object", "Invalid received data");
         }
         ContextRequest cr = new ContextRequest();
@@ -86,7 +86,7 @@
             ArrayNode events = (ArrayNode) eventsNode;
             List<Event> filteredEvents = new ArrayList<>();
             for (JsonNode event : events) {
-                if (schemaService.isValid(event, "https://unomi.apache.org/schemas/json/events/" + event.get("eventType").textValue() + "/1-0-0")) {
+                if (schemaService.isValid(event.toString(), "https://unomi.apache.org/schemas/json/events/" + event.get("eventType").textValue() + "/1-0-0")) {
                     filteredEvents.add(jsonParser.getCodec().treeToValue(event, Event.class));
                 }
             }
diff --git a/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java
index 0f23419..20d808d 100644
--- a/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java
+++ b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java
@@ -24,8 +24,8 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import org.apache.unomi.api.Event;
 import org.apache.unomi.api.EventsCollectorRequest;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.rest.exception.InvalidRequestException;
+import org.apache.unomi.schema.api.SchemaService;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -50,7 +50,7 @@
     @Override
     public EventsCollectorRequest deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
         JsonNode node = jsonParser.getCodec().readTree(jsonParser);
-        if (!schemaService.isValid(node, "https://unomi.apache.org/schemas/json/eventscollectorrequest/1-0-0")) {
+        if (!schemaService.isValid(node.toString(), "https://unomi.apache.org/schemas/json/eventscollectorrequest/1-0-0")) {
             throw new InvalidRequestException("Invalid events collector object", "Invalid received data");
         }
 
@@ -59,7 +59,7 @@
         final JsonNode eventsNode = node.get("events");
         if (eventsNode instanceof ArrayNode) {
             for (JsonNode event : eventsNode) {
-                if (schemaService.isValid(event, "https://unomi.apache.org/schemas/json/events/" + event.get("eventType").textValue() + "/1-0-0")) {
+                if (schemaService.isValid(event.toString(), "https://unomi.apache.org/schemas/json/events/" + event.get("eventType").textValue() + "/1-0-0")) {
                     filteredEvents.add(jsonParser.getCodec().treeToValue(event, Event.class));
                 }
             }
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index 3242165..3f84bf1 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -18,7 +18,6 @@
 package org.apache.unomi.rest.endpoints;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
@@ -26,10 +25,10 @@
 import org.apache.unomi.api.*;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.services.*;
-import org.apache.unomi.api.utils.ValidationPattern;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.rest.exception.InvalidRequestException;
 import org.apache.unomi.rest.service.RestServiceUtils;
+import org.apache.unomi.schema.api.SchemaService;
 import org.apache.unomi.utils.Changes;
 import org.apache.unomi.utils.HttpUtils;
 import org.osgi.service.component.annotations.Component;
@@ -43,8 +42,6 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.validation.Valid;
-import javax.validation.constraints.Pattern;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
@@ -57,7 +54,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import java.util.Objects;
 
 @WebService
 @Consumes(MediaType.APPLICATION_JSON)
@@ -164,7 +160,7 @@
         if (sessionId != null) {
             paramsAsJson.put("sessionId", sessionId);
         }
-        if (!schemaService.isValid(paramsAsJson, "https://unomi.apache.org/schemas/json/contextrequestparams/1-0-0")) {
+        if (!schemaService.isValid(paramsAsJson.toString(), "https://unomi.apache.org/schemas/json/contextrequestparams/1-0-0")) {
             throw new InvalidRequestException("Invalid parameter", "Invalid received data");
         }
         Date timestamp = new Date();
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaExtensionEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaExtensionEndPoint.java
deleted file mode 100644
index 5033124..0000000
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaExtensionEndPoint.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.unomi.rest.endpoints;
-
-import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.services.SchemaService;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.jws.WebMethod;
-import javax.jws.WebService;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.io.IOException;
-import java.util.List;
-
-@WebService
-@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
-@Path("/jsonSchemaExtension")
-@Component(service = JsonSchemaExtensionEndPoint.class, property = "osgi.jaxrs.resource=true")
-public class JsonSchemaExtensionEndPoint {
-
-    private static final Logger logger = LoggerFactory.getLogger(JsonSchemaExtensionEndPoint.class.getName());
-
-    @Reference
-    private SchemaService schemaService;
-
-    public JsonSchemaExtensionEndPoint() {
-        logger.info("Initializing JSON schema extension endpoint...");
-    }
-
-    @WebMethod(exclude = true)
-    public void setSchemaService(SchemaService schemaService) {
-        this.schemaService = schemaService;
-    }
-
-    /**
-     * Retrieves the 50 first json schema extension metadatas by default.
-     *
-     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
-     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
-     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering
-     *               elements according to the property order in the
-     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
-     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
-     * @return a List of the 50 first json schema metadata
-     */
-    @GET
-    @Path("/")
-    public List<Metadata> getJsonSchemaExtensionsMetadatas(@QueryParam("offset") @DefaultValue("0") int offset,
-            @QueryParam("size") @DefaultValue("50") int size, @QueryParam("sort") String sortBy) {
-        return schemaService.getJsonSchemaExtensionsMetadatas(offset, size, sortBy).getList();
-    }
-
-    /**
-     * Save a JSON schema extension
-     *
-     * @param jsonSchemaExtension the schema as string to save
-     * @return Response of the API call
-     */
-    @POST
-    @Path("/")
-    @Consumes(MediaType.TEXT_PLAIN)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response save(String jsonSchemaExtension) throws IOException {
-        schemaService.saveExtension(jsonSchemaExtension);
-        return Response.ok().build();
-    }
-
-    /**
-     * Deletes a JSON schema extension.
-     * The id is a Base64 id as the id have is basically an URL
-     *
-     * @param id the identifier of the JSON schema that we want to delete
-     */
-    @DELETE
-    @Path("/{id}")
-    public void remove(@PathParam("id") String id) {
-        schemaService.deleteExtension(id);
-    }
-}
diff --git a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
index 0da55b9..f2b85fe 100644
--- a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
+++ b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
@@ -30,7 +30,6 @@
 import org.apache.unomi.api.ContextRequest;
 import org.apache.unomi.api.EventsCollectorRequest;
 import org.apache.unomi.api.services.ConfigSharingService;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.rest.authentication.AuthenticationFilter;
 import org.apache.unomi.rest.authentication.AuthorizingInterceptor;
 import org.apache.unomi.rest.authentication.RestAuthenticationConfig;
@@ -38,6 +37,7 @@
 import org.apache.unomi.rest.deserializers.EventsCollectorRequestDeserializer;
 import org.apache.unomi.rest.server.provider.RetroCompatibilityParamConverterProvider;
 import org.apache.unomi.rest.validation.request.RequestValidatorInterceptor;
+import org.apache.unomi.schema.api.SchemaService;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Filter;
 import org.osgi.framework.ServiceReference;
diff --git a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
index 4600f0c..ac617d3 100644
--- a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
+++ b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
@@ -17,7 +17,6 @@
 package org.apache.unomi.rest.service.impl;
 
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-import com.fasterxml.jackson.databind.node.TextNode;
 import org.apache.unomi.api.Event;
 import org.apache.unomi.api.Persona;
 import org.apache.unomi.api.Profile;
@@ -25,9 +24,9 @@
 import org.apache.unomi.api.services.ConfigSharingService;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.api.services.PrivacyService;
-import org.apache.unomi.api.services.SchemaService;
 import org.apache.unomi.rest.exception.InvalidRequestException;
 import org.apache.unomi.rest.service.RestServiceUtils;
+import org.apache.unomi.schema.api.SchemaService;
 import org.apache.unomi.utils.Changes;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -38,7 +37,6 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.BadRequestException;
 import java.util.Date;
 import java.util.List;
 
@@ -56,7 +54,8 @@
     @Reference
     private EventService eventService;
 
-    @Reference SchemaService schemaService;
+    @Reference
+    SchemaService schemaService;
 
     public String getProfileIdCookieValue(HttpServletRequest httpServletRequest) {
         String cookieProfileId = null;
@@ -67,7 +66,7 @@
             for (Cookie cookie : cookies) {
                 final Object profileIdCookieName = configSharingService.getProperty("profileIdCookieName");
                 if (profileIdCookieName.equals(cookie.getName())) {
-                    if (!schemaService.isValid(JsonNodeFactory.instance.objectNode().put("profileIdCookieName", cookie.getValue()), "https://unomi.apache.org/schemas/json/cookie/1-0-0")) {
+                    if (!schemaService.isValid(JsonNodeFactory.instance.objectNode().put("profileIdCookieName", cookie.getValue()).toString(), "https://unomi.apache.org/schemas/json/cookie/1-0-0")) {
                         throw new InvalidRequestException("Invalid profile ID format in cookie", "Invalid received data");
                     }
                     cookieProfileId = cookie.getValue();
diff --git a/services/src/main/resources/META-INF/cxs/schemas/contextrequest.json b/rest/src/main/resources/META-INF/cxs/schemas/contextrequest.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/contextrequest.json
rename to rest/src/main/resources/META-INF/cxs/schemas/contextrequest.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/contextrequestparams.json b/rest/src/main/resources/META-INF/cxs/schemas/contextrequestparams.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/contextrequestparams.json
rename to rest/src/main/resources/META-INF/cxs/schemas/contextrequestparams.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/cookie.json b/rest/src/main/resources/META-INF/cxs/schemas/cookie.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/cookie.json
rename to rest/src/main/resources/META-INF/cxs/schemas/cookie.json
diff --git a/services/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json b/rest/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
similarity index 100%
rename from services/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
rename to rest/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
diff --git a/services/pom.xml b/services/pom.xml
index c4acbb2..3f41968 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -171,13 +171,6 @@
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
-
-        <dependency>
-            <groupId>com.networknt</groupId>
-            <artifactId>json-schema-validator</artifactId>
-            <version>1.0.49</version>
-        </dependency>
-
     </dependencies>
 
     <build>
@@ -191,10 +184,6 @@
                         <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
                         <Import-Package>
                             sun.misc;resolution:=optional,
-                            org.jcodings;resolution:=optional,
-                            org.jcodings.specific;resolution:=optional,
-                            org.joni;resolution:=optional,
-                            org.joni.exception;resolution:=optional,
                             *
                         </Import-Package>
                     </instructions>
diff --git a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index 736de11..5b81819 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -65,8 +65,6 @@
 
     private BundleContext bundleContext;
 
-    private SchemaService schemaService;
-
     private Set<String> predefinedEventTypeIds = new LinkedHashSet<String>();
 
     private Set<String> restrictedEventTypeIds = new LinkedHashSet<String>();
@@ -114,9 +112,6 @@
         this.shouldBeCheckedEventSourceId = shouldBeCheckedEventSourceId;
     }
 
-    public void setSchemaService(SchemaService schemaService) {
-        this.schemaService = schemaService;
-    }
     public void setPersistenceService(PersistenceService persistenceService) {
         this.persistenceService = persistenceService;
     }
diff --git a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
deleted file mode 100644
index 56d70f2..0000000
--- a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.unomi.services.impl.schemas;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.networknt.schema.JsonMetaSchema;
-import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.NonValidationKeyword;
-import com.networknt.schema.SpecVersion;
-import com.networknt.schema.ValidationMessage;
-import com.networknt.schema.uri.URIFetcher;
-import org.apache.commons.io.IOUtils;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.schema.JSONSchemaExtension;
-import org.apache.unomi.api.schema.UnomiJSONSchema;
-import org.apache.unomi.api.schema.json.JSONSchema;
-import org.apache.unomi.api.schema.json.JSONTypeFactory;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.api.services.SchedulerService;
-import org.apache.unomi.api.services.SchemaService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-import org.apache.unomi.persistence.spi.PersistenceService;
-import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TimerTask;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-public class SchemaServiceImpl implements SchemaService {
-
-    private static final String URI = "https://json-schema.org/draft/2019-09/schema";
-
-    private static final Logger logger = LoggerFactory.getLogger(SchemaServiceImpl.class.getName());
-
-    private final Map<String, JSONSchema> predefinedUnomiJSONSchemaById = new HashMap<>();
-
-    private Map<String, JSONSchema> schemasById = new HashMap<>();
-
-    private Map<String, JSONSchemaExtension> extensionById = new HashMap<>();
-
-    private BundleContext bundleContext;
-
-    private ProfileService profileService;
-
-    private PersistenceService persistenceService;
-
-    private SchedulerService schedulerService;
-
-    private JsonSchemaFactory jsonSchemaFactory;
-
-    ObjectMapper objectMapper = new ObjectMapper();
-
-    private ScheduledFuture<?> scheduledFuture;
-
-    private Integer jsonSchemaRefreshInterval = 1000;
-
-    public void setPersistenceService(PersistenceService persistenceService) {
-        this.persistenceService = persistenceService;
-    }
-
-    public void setSchedulerService(SchedulerService schedulerService) {
-        this.schedulerService = schedulerService;
-    }
-
-    public void setJsonSchemaRefreshInterval(Integer jsonSchemaRefreshInterval) {
-        this.jsonSchemaRefreshInterval = jsonSchemaRefreshInterval;
-    }
-
-    @Override
-    public PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy) {
-        PartialList<UnomiJSONSchema> items = persistenceService.getAllItems(UnomiJSONSchema.class, offset, size, sortBy);
-        List<Metadata> details = new LinkedList<>();
-        for (UnomiJSONSchema definition : items.getList()) {
-            details.add(definition.getMetadata());
-        }
-        return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize(), items.getTotalSizeRelation());
-    }
-
-    @Override
-    public boolean isValid(JsonNode jsonNode, String schemaId) {
-        String schemaAsString;
-        JsonSchema jsonSchema = null;
-        try {
-            JSONSchema validationSchema = schemasById.get(schemaId);
-            if (validationSchema != null) {
-                schemaAsString = objectMapper.writeValueAsString(schemasById.get(schemaId).getSchemaTree());
-                jsonSchema = jsonSchemaFactory.getSchema(schemaAsString);
-            } else {
-                logger.warn("No schema found for {}", schemaId);
-            }
-        } catch (JsonProcessingException e) {
-            logger.error("Failed to process json schema", e);
-        }
-
-        if (jsonSchema != null) {
-
-            Set<ValidationMessage> validationMessages = jsonSchema.validate(jsonNode);
-            if (validationMessages == null || validationMessages.isEmpty()) {
-                return true;
-            }
-            for (ValidationMessage validationMessage : validationMessages) {
-                logger.error("Error validating object against schema {}: {}", schemaId, validationMessage);
-            }
-            return false;
-        }
-        return false;
-    }
-
-    @Override
-    public List<JSONSchema> getSchemasByTarget(String target) {
-        return schemasById.values().stream().filter(jsonSchema -> jsonSchema.getTarget() != null && jsonSchema.getTarget().equals(target))
-                .collect(Collectors.toList());
-
-    }
-
-    @Override
-    public void saveSchema(String schema) {
-        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
-        if (jsonSchema.getSchemaNode().at("/self/target").asText().equals("events") && !jsonSchema.getSchemaNode().at("/self/name").asText()
-                .matches("[_A-Za-z][_0-9A-Za-z]*")) {
-            throw new IllegalArgumentException(
-                    "The \"/self/name\" value should match the following regular expression [_A-Za-z][_0-9A-Za-z]* for the Json schema on"
-                            + " events");
-        }
-        if (predefinedUnomiJSONSchemaById.get(jsonSchema.getSchemaNode().get("$id").asText()) == null) {
-            persistenceService.save(buildUnomiJsonSchema(schema));
-            JSONSchema localSchema = buildJSONSchema(jsonSchema);
-            schemasById.put(jsonSchema.getSchemaNode().get("$id").asText(), localSchema);
-        } else {
-            logger.error("Can not store a JSON Schema which have the id of a schema preovided by Unomi");
-        }
-    }
-
-    @Override
-    public void saveSchema(InputStream schemaStream) throws IOException {
-        saveSchema(IOUtils.toString(schemaStream));
-    }
-
-    @Override
-    public void loadPredefinedSchema(InputStream schemaStream) {
-        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schemaStream);
-        JSONSchema localJsonSchema = buildJSONSchema(jsonSchema);
-
-        predefinedUnomiJSONSchemaById.put(jsonSchema.getSchemaNode().get("$id").asText(), localJsonSchema);
-        schemasById.put(jsonSchema.getSchemaNode().get("$id").asText(), localJsonSchema);
-    }
-
-    @Override
-    public boolean deleteSchema(String schemaId) {
-        schemasById.remove(schemaId);
-        return persistenceService.remove(schemaId, UnomiJSONSchema.class);
-    }
-
-    @Override
-    public boolean deleteSchema(InputStream schemaStream) {
-        JsonNode schemaNode = jsonSchemaFactory.getSchema(schemaStream).getSchemaNode();
-        return deleteSchema(schemaNode.get("$id").asText());
-    }
-
-    @Override
-    public void saveExtension(InputStream extensionStream) throws IOException {
-        saveExtension(IOUtils.toString(extensionStream));
-    }
-
-    @Override
-    public void saveExtension(String extension) throws IOException {
-        JSONSchemaExtension jsonSchemaExtension = buildExtension(extension);
-        persistenceService.save(jsonSchemaExtension);
-        extensionById.put(jsonSchemaExtension.getId(), jsonSchemaExtension);
-    }
-
-    @Override
-    public boolean deleteExtension(InputStream extensionStream) throws IOException {
-        JsonNode jsonNode = objectMapper.readTree(extensionStream);
-        return deleteExtension(jsonNode.get("id").asText());
-    }
-
-    @Override
-    public boolean deleteExtension(String extensionId) {
-        extensionById.remove(extensionId);
-        return persistenceService.remove(extensionId, JSONSchemaExtension.class);
-    }
-
-    @Override
-    public PartialList<Metadata> getJsonSchemaExtensionsMetadatas(int offset, int size, String sortBy) {
-        PartialList<JSONSchemaExtension> items = persistenceService.getAllItems(JSONSchemaExtension.class, offset, size, sortBy);
-        List<Metadata> details = new LinkedList<>();
-        for (JSONSchemaExtension definition : items.getList()) {
-            details.add(definition.getMetadata());
-        }
-        return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize(), items.getTotalSizeRelation());
-    }
-
-    private JSONSchemaExtension buildExtension(String extension) throws JsonProcessingException {
-        JsonNode jsonNode = objectMapper.readTree(extension);
-        JSONSchemaExtension jsonSchemaExtension = new JSONSchemaExtension();
-        jsonSchemaExtension.setId(jsonNode.get("id").asText());
-        jsonSchemaExtension.setSchemaId(jsonNode.get("schemaId").asText());
-        jsonSchemaExtension.setExtension(jsonNode.get("extension").toString());
-        jsonSchemaExtension.setPriority(jsonNode.get("priority").asDouble());
-        Metadata metadata = new Metadata();
-        metadata.setId(jsonNode.get("id").asText());
-        metadata.setDescription(jsonNode.get("description").asText());
-        metadata.setName(jsonNode.get("name").asText());
-        jsonSchemaExtension.setMetadata(metadata);
-        return jsonSchemaExtension;
-    }
-
-    @Override
-    public JSONSchema getSchema(String schemaId) {
-        return schemasById.get(schemaId);
-    }
-
-    private JSONSchema buildJSONSchema(JsonSchema jsonSchema) {
-        return Optional.of(jsonSchema).map(jsonSchemaToProcess -> {
-            try {
-                return (Map<String, Object>) objectMapper.treeToValue(jsonSchemaToProcess.getSchemaNode(), Map.class);
-            } catch (JsonProcessingException e) {
-                logger.error("Failed to process Json object, e");
-            }
-            return Collections.<String, Object>emptyMap();
-        }).map(jsonSchemaToProcess -> {
-            JSONSchema schema = new JSONSchema(jsonSchemaToProcess, new JSONTypeFactory(this));
-            schema.setPluginId(bundleContext.getBundle().getBundleId());
-            return schema;
-        }).get();
-    }
-
-    private UnomiJSONSchema buildUnomiJsonSchema(String schema) {
-        JsonNode schemaNode = jsonSchemaFactory.getSchema(schema).getSchemaNode();
-        return new UnomiJSONSchema(schemaNode.get("$id").asText(), schema, schemaNode.at("/self/target").asText());
-    }
-
-    public JsonSchema getJsonSchema(String schemaId) {
-        String schemaAsString = null;
-        try {
-            schemaAsString = objectMapper.writeValueAsString(schemasById.get(schemaId).getSchemaTree());
-        } catch (JsonProcessingException e) {
-            logger.error("Failed to process json schema", e);
-        }
-        return jsonSchemaFactory.getSchema(schemaAsString);
-    }
-
-    private URIFetcher getUriFetcher() {
-        return uri -> {
-            logger.debug("Fetching schema {}", uri);
-            String schemaAsString = null;
-            try {
-                schemaAsString = objectMapper.writeValueAsString(schemasById.get(uri.toString()).getSchemaTree());
-            } catch (JsonProcessingException e) {
-                logger.error("Failed to process json schema", e);
-            }
-            JsonSchema schema = jsonSchemaFactory.getSchema(schemaAsString);
-            if (schema == null) {
-                logger.error("Couldn't find schema {}", uri);
-                return null;
-            }
-            return IOUtils.toInputStream(schema.getSchemaNode().asText());
-        };
-    }
-
-    private void refreshJSONSchemas() {
-        schemasById = new HashMap<>();
-        schemasById.putAll(predefinedUnomiJSONSchemaById);
-        persistenceService.getAllItems(UnomiJSONSchema.class).forEach(
-                jsonSchema -> schemasById.put(jsonSchema.getId(), buildJSONSchema(jsonSchemaFactory.getSchema(jsonSchema.getSchema()))));
-    }
-
-    private void refreshJSONSchemasExtensions() {
-        extensionById = new HashMap<>();
-        persistenceService.getAllItems(JSONSchemaExtension.class).forEach(extension -> extensionById.put(extension.getId(), extension));
-    }
-
-    private void initializeTimers() {
-        TimerTask task = new TimerTask() {
-            @Override
-            public void run() {
-                refreshJSONSchemas();
-                refreshJSONSchemasExtensions();
-            }
-        };
-        scheduledFuture = schedulerService.getScheduleExecutorService()
-                .scheduleWithFixedDelay(task, 0, jsonSchemaRefreshInterval, TimeUnit.MILLISECONDS);
-    }
-
-    public void init() {
-        JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
-                .addKeyword(new UnomiPropertyTypeKeyword(profileService, this)).addKeyword(new NonValidationKeyword("self")).build();
-        jsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
-                .addMetaSchema(jsonMetaSchema).defaultMetaSchemaURI(URI).uriFetcher(getUriFetcher(), "https", "http").build();
-
-        initializeTimers();
-        logger.info("Schema service initialized.");
-    }
-
-    public void destroy() {
-        scheduledFuture.cancel(true);
-        logger.info("Schema service shutdown.");
-    }
-
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-    }
-
-    public void setProfileService(ProfileService profileService) {
-        this.profileService = profileService;
-    }
-}
diff --git a/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java b/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java
deleted file mode 100644
index 973d4ac..0000000
--- a/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.unomi.services.impl.schemas;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.networknt.schema.AbstractJsonValidator;
-import com.networknt.schema.AbstractKeyword;
-import com.networknt.schema.CustomErrorMessageType;
-import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaException;
-import com.networknt.schema.JsonValidator;
-import com.networknt.schema.ValidationContext;
-import com.networknt.schema.ValidationMessage;
-import org.apache.unomi.api.PropertyType;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.api.services.SchemaService;
-
-import java.text.MessageFormat;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-class UnomiPropertyTypeKeyword extends AbstractKeyword {
-
-    private final ProfileService profileService;
-    private final SchemaServiceImpl schemaService;
-
-    private static final class UnomiPropertyTypeJsonValidator extends AbstractJsonValidator {
-
-        String schemaPath;
-        JsonNode schemaNode;
-        JsonSchema parentSchema;
-        ValidationContext validationContext;
-        ProfileService profileService;
-        SchemaServiceImpl schemaService;
-
-        public UnomiPropertyTypeJsonValidator(String keyword, String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                ValidationContext validationContext, ProfileService profileService, SchemaServiceImpl schemaService) {
-            super(keyword);
-            this.schemaPath = schemaPath;
-            this.schemaNode = schemaNode;
-            this.parentSchema = parentSchema;
-            this.validationContext = validationContext;
-            this.profileService = profileService;
-            this.schemaService = schemaService;
-        }
-
-        @Override
-        public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-            Set<ValidationMessage> validationMessages = new HashSet<>();
-            Iterator<String> fieldNames = node.fieldNames();
-            while (fieldNames.hasNext()) {
-                String fieldName = fieldNames.next();
-                PropertyType propertyType = getPropertyType(fieldName);
-                if (propertyType == null) {
-                    validationMessages.add(buildValidationMessage(CustomErrorMessageType
-                            .of("property-not-found", new MessageFormat("{0} : Couldn''t find property type with id={1}")), at, fieldName));
-                } else {
-                    // @todo further validation, if it can be used in this context (event, profile, session)
-                    String valueTypeId = propertyType.getValueTypeId();
-                    JsonSchema jsonSchema = schemaService
-                            .getJsonSchema("https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json");
-                    if (jsonSchema == null) {
-                        validationMessages.add(buildValidationMessage(CustomErrorMessageType
-                                        .of("value-schema-not-found", new MessageFormat("{0} : Couldn''t find schema type with id={1}")), at,
-                                "https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json"));
-                    } else {
-                        Set<ValidationMessage> propertyValidationMessages = jsonSchema.validate(node.get(fieldName));
-                        if (propertyValidationMessages != null) {
-                            validationMessages.addAll(propertyValidationMessages);
-                        }
-                    }
-                }
-            }
-            return validationMessages;
-        }
-
-        private PropertyType getPropertyType(String fieldName) {
-            Map<String, PropertyType> propertyTypes = new HashMap<>();
-            if (schemaNode.size() > 0) {
-                for (Iterator<JsonNode> it = schemaNode.iterator(); it.hasNext(); ) {
-                    JsonNode target = it.next();
-                    if ("_all".equals(target.asText())) {
-                        return profileService.getPropertyType(fieldName);
-                    } else {
-                        Collection<PropertyType> targetPropertyTypes = profileService.getTargetPropertyTypes(target.asText());
-                        targetPropertyTypes.stream().map(propertyType -> propertyTypes.put(propertyType.getItemId(), propertyType));
-                    }
-                }
-                return propertyTypes.get(fieldName);
-            } else {
-                return profileService.getPropertyType(fieldName);
-            }
-        }
-    }
-
-    public UnomiPropertyTypeKeyword(ProfileService profileService, SchemaServiceImpl schemaService) {
-        super("unomiPropertyTypes");
-        this.profileService = profileService;
-        this.schemaService = schemaService;
-    }
-
-    @Override
-    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)
-            throws JsonSchemaException, Exception {
-        return new UnomiPropertyTypeJsonValidator(this.getValue(), schemaPath, schemaNode, parentSchema, validationContext, profileService,
-                schemaService);
-    }
-}
diff --git a/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java b/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java
deleted file mode 100644
index 21dab22..0000000
--- a/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.unomi.services.listener;
-
-import org.apache.unomi.api.schema.JSONSchemaExtension;
-import org.apache.unomi.api.schema.UnomiJSONSchema;
-import org.apache.unomi.api.services.SchemaService;
-import org.apache.unomi.persistence.spi.PersistenceService;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.SynchronousBundleListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Enumeration;
-
-/**
- * An implementation of a BundleListener for the JSON schema.
- * It will load the pre-defined schema files in the folder META-INF/cxs/schemas.
- * It will load the extension of schema in the folder META-INF/cxs/schemasextensions.
- * The scripts will be stored in the ES index jsonSchema and the extension will be stored in jsonSchemaExtension
- */
-public class JsonSchemaListener implements SynchronousBundleListener {
-
-    private static final Logger logger = LoggerFactory.getLogger(JsonSchemaListener.class.getName());
-    public static final String ENTRIES_LOCATION = "META-INF/cxs/schemas";
-
-    public static final String EXTENSIONS_ENTRIES_LOCATION = "META-INF/cxs/schemasextensions";
-
-    private PersistenceService persistenceService;
-
-    private SchemaService schemaService;
-
-    private BundleContext bundleContext;
-
-    public void setPersistenceService(PersistenceService persistenceService) {
-        this.persistenceService = persistenceService;
-    }
-
-    public void setSchemaService(SchemaService schemaService) {
-        this.schemaService = schemaService;
-    }
-
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-    }
-
-    public void postConstruct() {
-        logger.info("JSON schema listener initializing...");
-        logger.debug("postConstruct {}", bundleContext.getBundle());
-        createIndexes();
-
-        loadPredefinedSchemas(bundleContext);
-
-        for (Bundle bundle : bundleContext.getBundles()) {
-            if (bundle.getBundleContext() != null && bundle.getBundleId() != bundleContext.getBundle().getBundleId()) {
-                saveSchemas(bundle.getBundleContext());
-                saveExtensions(bundle.getBundleContext());
-            }
-        }
-
-        bundleContext.addBundleListener(this);
-        logger.info("JSON schema listener initialized.");
-    }
-
-    public void preDestroy() {
-        bundleContext.removeBundleListener(this);
-        logger.info("JSON schema listener shutdown.");
-    }
-
-    private void processBundleStartup(BundleContext bundleContext) {
-        if (bundleContext == null) {
-            return;
-        }
-        saveSchemas(bundleContext);
-    }
-
-    private void processBundleStop(BundleContext bundleContext) {
-        if (bundleContext == null) {
-            return;
-        }
-        unloadSchemas(bundleContext);
-        unloadExtensions(bundleContext);
-    }
-
-    public void bundleChanged(BundleEvent event) {
-        switch (event.getType()) {
-            case BundleEvent.STARTED:
-                processBundleStartup(event.getBundle().getBundleContext());
-                break;
-            case BundleEvent.STOPPING:
-                if (!event.getBundle().getSymbolicName().equals(bundleContext.getBundle().getSymbolicName())) {
-                    processBundleStop(event.getBundle().getBundleContext());
-                }
-                break;
-        }
-    }
-
-    public void createIndexes() {
-        if (persistenceService.createIndex(UnomiJSONSchema.ITEM_TYPE)) {
-            logger.info("{} index created", UnomiJSONSchema.ITEM_TYPE);
-        } else {
-            logger.info("{} index already exists", UnomiJSONSchema.ITEM_TYPE);
-        }
-        if (persistenceService.createIndex(JSONSchemaExtension.ITEM_TYPE)) {
-            logger.info("{} index created", JSONSchemaExtension.ITEM_TYPE);
-        } else {
-            logger.info("{} index already exists", JSONSchemaExtension.ITEM_TYPE);
-        }
-    }
-
-    private void saveSchemas(BundleContext bundleContext) {
-        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
-        if (predefinedSchemas == null) {
-            return;
-        }
-
-        while (predefinedSchemas.hasMoreElements()) {
-            URL predefinedSchemaURL = predefinedSchemas.nextElement();
-            logger.debug("Found JSON schema at {}, loading... ", predefinedSchemaURL);
-
-            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
-                schemaService.saveSchema(schemaInputStream);
-            } catch (Exception e) {
-                logger.error("Error while loading schema definition {}", predefinedSchemaURL, e);
-            }
-        }
-    }
-
-    private void loadPredefinedSchemas(BundleContext bundleContext) {
-        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
-        if (predefinedSchemas == null) {
-            return;
-        }
-
-        while (predefinedSchemas.hasMoreElements()) {
-            URL predefinedSchemaURL = predefinedSchemas.nextElement();
-            logger.debug("Found predefined JSON schema at {}, loading... ", predefinedSchemaURL);
-            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
-                schemaService.loadPredefinedSchema(schemaInputStream);
-            } catch (Exception e) {
-                logger.error("Error while loading schema definition {}", predefinedSchemaURL, e);
-            }
-        }
-    }
-
-    private void unloadSchemas(BundleContext bundleContext) {
-        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
-        if (predefinedSchemas == null) {
-            return;
-        }
-
-        while (predefinedSchemas.hasMoreElements()) {
-            URL predefinedSchemaURL = predefinedSchemas.nextElement();
-            logger.debug("Found predefined JSON schema at {}, loading... ", predefinedSchemaURL);
-
-            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
-                schemaService.deleteSchema(schemaInputStream);
-            } catch (Exception e) {
-                logger.error("Error while removing schema at {}", predefinedSchemaURL, e);
-            }
-        }
-    }
-
-    private void saveExtensions(BundleContext bundleContext) {
-        Enumeration<URL> extensions = bundleContext.getBundle().findEntries(EXTENSIONS_ENTRIES_LOCATION, "*.json", true);
-        if (extensions == null) {
-            return;
-        }
-
-        while (extensions.hasMoreElements()) {
-            URL extensionURL = extensions.nextElement();
-            logger.debug("Found JSON schema extension at {}, loading... ", extensionURL);
-
-            try (InputStream extensionInputStream = extensionURL.openStream()) {
-                schemaService.saveExtension(extensionInputStream);
-            } catch (Exception e) {
-                logger.error("Error while loading schema extension at {}", extensionURL, e);
-            }
-        }
-    }
-
-    private void unloadExtensions(BundleContext bundleContext) {
-        Enumeration<URL> extensions = bundleContext.getBundle().findEntries(EXTENSIONS_ENTRIES_LOCATION, "*.json", true);
-        if (extensions == null) {
-            return;
-        }
-
-        while (extensions.hasMoreElements()) {
-            URL predefinedSchemaURL = extensions.nextElement();
-            logger.debug("Found JSON schema extension at {}, loading... ", predefinedSchemaURL);
-
-            try (InputStream extensionInputStream = predefinedSchemaURL.openStream()) {
-                schemaService.deleteExtension(extensionInputStream);
-            } catch (Exception e) {
-                logger.error("Error while loading schema extension at {}", predefinedSchemaURL, e);
-            }
-        }
-    }
-}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 1d1417c..68ce68e 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -45,7 +45,6 @@
             <cm:property name="events.shouldBeCheckedEventSourceId" value="false"/>
             <cm:property name="rules.optimizationActivated" value="true"/>
             <cm:property name="schedules.thread.poolSize" value="5"/>
-            <cm:property name="json.schema.refresh.interval" value="1000"/>
         </cm:default-properties>
     </cm:property-placeholder>
 
@@ -102,34 +101,11 @@
         </interfaces>
     </service>
 
-    <bean id="schemaServiceImpl" class="org.apache.unomi.services.impl.schemas.SchemaServiceImpl" init-method="init"
-          destroy-method="destroy">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="profileService" ref="profileServiceImpl"/>
-        <property name="persistenceService" ref="persistenceService"/>
-        <property name="schedulerService" ref="schedulerServiceImpl"/>
-        <property name="jsonSchemaRefreshInterval" value="${services.json.schema.refresh.interval}"/>
-    </bean>
-    <service id="schemaService" ref="schemaServiceImpl" interface="org.apache.unomi.api.services.SchemaService"/>
-
-    <bean id="jsonSchemaListenerImpl" class="org.apache.unomi.services.listener.JsonSchemaListener"
-          init-method="postConstruct" destroy-method="preDestroy">
-        <property name="persistenceService" ref="persistenceService"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="schemaService" ref="schemaServiceImpl"/>
-    </bean>
-    <service id="jsonSchemaListener" ref="jsonSchemaListenerImpl">
-        <interfaces>
-            <value>org.osgi.framework.SynchronousBundleListener</value>
-        </interfaces>
-    </service>
-
     <bean id="eventServiceImpl" class="org.apache.unomi.services.impl.events.EventServiceImpl">
         <property name="persistenceService" ref="persistenceService"/>
         <property name="definitionsService" ref="definitionsServiceImpl"/>
         <property name="sourceService" ref="sourceServiceImpl"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="schemaService" ref="schemaServiceImpl"/>
         <property name="predefinedEventTypeIds">
             <set>
                 <value>view</value>
diff --git a/services/src/main/resources/org.apache.unomi.services.cfg b/services/src/main/resources/org.apache.unomi.services.cfg
index 1f9a9bd..452b759 100644
--- a/services/src/main/resources/org.apache.unomi.services.cfg
+++ b/services/src/main/resources/org.apache.unomi.services.cfg
@@ -77,6 +77,3 @@
 
 # The number of threads to compose the pool size of the scheduler.
 scheduler.thread.poolSize=${org.apache.unomi.scheduler.thread.poolSize:-5}
-
-# The interval in milliseconds to reload the json schemas in memory
-services.json.schema.refresh.interval=${org.apache.unomi.json.schema.refresh.interval:-1000}
diff --git a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index a6223d5..3e0ab69 100644
--- a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -44,6 +44,8 @@
                 <value>org.apache.unomi.cxs-geonames-rest</value>
                 <value>org.apache.unomi.cxs-privacy-extension-services</value>
                 <value>org.apache.unomi.cxs-privacy-extension-rest</value>
+                <value>org.apache.unomi.json-schema-services</value>
+                <value>org.apache.unomi.json-schema-rest</value>
                 <value>org.apache.unomi.rest</value>
                 <value>org.apache.unomi.wab</value>
                 <value>org.apache.unomi.plugins-base</value>