diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index f23e469e..5ef5e9d5 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-* @tsurdilo @manuelstein @ricardozanini
\ No newline at end of file
+* @ricardozanini @fjtirado
\ No newline at end of file
diff --git a/.github/OWNERS b/.github/OWNERS
index da3ddc3f..0db9cb96 100644
--- a/.github/OWNERS
+++ b/.github/OWNERS
@@ -1,10 +1,8 @@
 reviewers:
-  - tsurdilo
-  - manuelstein
   - ricardozanini
+  - fjtirado
 approvers:
-  - tsurdilo
-  - manuelstein
   - ricardozanini
+  - fjtirado
 labels:
   - sig/contributor-experience
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..907b5f95
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "maven" # See documentation for possible values
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "weekly"
+    assignees:
+      - ricardozanini
+      - fjtirado
\ No newline at end of file
diff --git a/.github/project.yml b/.github/project.yml
new file mode 100644
index 00000000..016dfafa
--- /dev/null
+++ b/.github/project.yml
@@ -0,0 +1,3 @@
+release:
+  current-version: 4.2.0.Final
+  next-version: 4.3.0-SNAPSHOT
diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml
deleted file mode 100644
index 67812473..00000000
--- a/.github/workflows/maven-deploy.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-# This workflow will build a Java project with Maven
-# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
-
-name: Deploy JAVA SDK
-
-on:
-  push:
-    branches:
-      - main
-jobs:
-  publish:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - name: Set up Maven Central Repository
-        uses: actions/setup-java@v1
-        with:
-          java-version: 1.8
-          server-id: ossrh
-          server-username: MAVEN_USERNAME
-          server-password: MAVEN_PASSWORD
-      - name: Publish package
-        run: mvn -B -f pom.xml deploy
-        env:
-          MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
-          MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml
index 5da52578..e5ed35ac 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -1,30 +1,28 @@
 # This workflow will build a Java project with Maven
 # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
 
-name: Verify JAVA SDK
+name: sdk-java Verify
 
 on:
   push:
     branches:
-      - main
+      - 4.*
   pull_request:
     branches:
-      - main
+      - 4.*
 jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - name: Set up JDK 1.8
-      uses: actions/setup-java@v1
-      with:
-        java-version: 1.8
-    - name: Cache Maven packages
-      uses: actions/cache@v2
-      with:
-        path: ~/.m2
-        key: ${{ runner.os }}-m2-${{ hashFiles('pom.xml') }}
-        restore-keys: ${{ runner.os }}-m2
-    - name: Verify with Maven
-      run: |
-        mvn -B -f pom.xml clean install verify
+      - uses: actions/checkout@v4
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v4
+        with:
+          distribution: temurin
+          java-version: 17
+          cache: 'maven'
+
+      - name: Verify with Maven
+        run: |
+          mvn -B -f pom.xml clean install verify
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
new file mode 100644
index 00000000..44f54117
--- /dev/null
+++ b/.github/workflows/pre-release.yml
@@ -0,0 +1,25 @@
+name: sdk-java Pre Release
+
+on:
+  pull_request:
+    paths:
+      - '.github/project.yml'
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    name: pre release
+
+    steps:
+      - uses: radcortez/project-metadata-action@main
+        name: retrieve project metadata
+        id: metadata
+        with:
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          metadata-file-path: '.github/project.yml'
+
+      - name: Validate version
+        if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT')
+        run: |
+          echo '::error::Cannot release a SNAPSHOT version.'
+          exit 1
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..2e4ec8ce
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,60 @@
+name: sdk-java Release
+
+on:
+  pull_request:
+    types: [closed]
+    paths:
+      - '.github/project.yml'
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    name: release
+    if: ${{ github.event.pull_request.merged == true }}
+
+    steps:
+      - uses: radcortez/project-metadata-action@main
+        name: Retrieve project metadata
+        id: metadata
+        with:
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          metadata-file-path: '.github/project.yml'
+
+      - uses: actions/checkout@v4
+
+      - name: Import GPG key
+        id: import_gpg
+        uses: crazy-max/ghaction-import-gpg@v5
+        with:
+          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+          passphrase: ${{ secrets.GPG_PASSPHRASE }}
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v4
+        with:
+          distribution: temurin
+          java-version: 17
+          cache: 'maven'
+          server-id: ossrh
+          server-username: MAVEN_USERNAME
+          server-password: MAVEN_PASSWORD
+
+      - name: Configure Git author
+        run: |
+          git config --local user.email "action@github.com"
+          git config --local user.name "GitHub Action"
+
+      - name: Maven release ${{steps.metadata.outputs.current-version}}
+        run: |
+          git checkout -b release
+          mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}}
+          cat release.properties
+          git checkout ${{github.base_ref}}
+          git rebase release
+          mvn -B release:perform -Prelease -Darguments="-DperformRelease"
+        env:
+          MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+          MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
+
+      - name: Push tags
+        run: git push && git push --tags
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d4dfde66..1dfd7048 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.DS_Store
 HELP.md
 target/
 !.mvn/wrapper/maven-wrapper.jar
diff --git a/README.md b/README.md
index efb425dc..7aef4f23 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,9 @@ to parse and validate workflow definitions as well as generate the workflow diag
 
 ### Status
 
-| Latest Releases | Conformance to spec version |
-| :---: | :---: |
-| 4.0.0-SNAPSHOT (main branch) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) |
+|                             Latest Releases                             | Conformance to spec version |
+|:-----------------------------------------------------------------------:| :---: |
+| [4.1.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) |
 | [3.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.7](https://github.com/serverlessworkflow/specification/tree/0.7.x) |
 | [2.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.6](https://github.com/serverlessworkflow/specification/tree/0.6.x) |
 | [1.0.3.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.5](https://github.com/serverlessworkflow/specification/tree/0.5.x) |
@@ -64,31 +64,31 @@ b) Add the following dependencies to your pom.xml `dependencies` section:
 <dependency>
     <groupId>io.serverlessworkflow</groupId>
     <artifactId>serverlessworkflow-api</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
+    <version>4.1.0.Final</version>
 </dependency>
 
 <dependency>
     <groupId>io.serverlessworkflow</groupId>
     <artifactId>serverlessworkflow-spi</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
+    <version>4.1.0.Final</version>
 </dependency>
 
 <dependency>
     <groupId>io.serverlessworkflow</groupId>
     <artifactId>serverlessworkflow-validation</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
+    <version>4.1.0.Final</version>
 </dependency>
 
 <dependency>
     <groupId>io.serverlessworkflow</groupId>
     <artifactId>serverlessworkflow-diagram</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
+    <version>4.1.0.Final</version>
 </dependency>
 
 <dependency>
     <groupId>io.serverlessworkflow</groupId>
     <artifactId>serverlessworkflow-util</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
+    <version>4.1.0.Final</version>
 </dependency>
 ```
 
@@ -103,11 +103,11 @@ maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
 b) Add the following dependencies to your build.gradle `dependencies` section:
 
 ```text
-implementation("io.serverlessworkflow:serverlessworkflow-api:4.0.0-SNAPSHOT")
-implementation("io.serverlessworkflow:serverlessworkflow-spi:4.0.0-SNAPSHOT")
-implementation("io.serverlessworkflow:serverlessworkflow-validation:4.0.0-SNAPSHOT")
-implementation("io.serverlessworkflow:serverlessworkflow-diagram:4.0.0-SNAPSHOT")
-implementation("io.serverlessworkflow:serverlessworkflow-util:4.0.0-SNAPSHOT")
+implementation("io.serverlessworkflow:serverlessworkflow-api:4.1.0.Final")
+implementation("io.serverlessworkflow:serverlessworkflow-spi:4.1.0.Final")
+implementation("io.serverlessworkflow:serverlessworkflow-validation:4.1.0.Final")
+implementation("io.serverlessworkflow:serverlessworkflow-diagram:4.1.0.Final")
+implementation("io.serverlessworkflow:serverlessworkflow-util:4.1.0.Final")
 ```
 
 ### How to Use 
@@ -267,6 +267,22 @@ String diagramSVG = workflowDiagram.getSvgDiagram();
 `diagramSVG` includes the diagram SVG source which you can then decide to save to a file, 
 print, or process further.
 
+In case default visualization of the workflow is not sufficient you can provide custom workflow template to be 
+used while generating the SVG file. Easiest is to start off from the default template and customize it to your needs.
+
+Custom template must be on the classpath in `templates/plantuml` directory and must use `.txt` extension. Next
+template is set on `WorkflowDiagram` instance as shown below.
+
+``` java
+Workflow workflow = Workflow.fromSource(source);
+
+WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl();
+workflowDiagram.setWorkflow(workflow);
+workflowDiagram.setTemplate("custom-template");
+
+String diagramSVG = workflowDiagram.getSvgDiagram();
+```
+
 By default the diagram legend is now shown. If you want to enable it you can do:
 
 ``` java
@@ -296,15 +312,18 @@ Here are some generated diagrams from the specification examples (with legend en
 Workflow utils provide a number of useful methods for extracting information from workflow definitions.
 Once you have a `Workflow` instance, you can use it
 ##### Get Starting State
-```Java
+
+```java
 State startingState = WorkflowUtils.getStartingState(workflow);
 ```
 ##### Get States by State Type
-```Java
+
+```java
     List<State> states = WorkflowUtils.getStates(workflow, DefaultState.Type.EVENT);
 ```
 ##### Get Consumed-Events, Produced-Events and their count
-```Java
+
+```java
  List<EventDefinition> consumedEvents = WorkflowUtils.getWorkflowConsumedEvents(workflow);
  int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);
 
@@ -312,7 +331,8 @@ State startingState = WorkflowUtils.getStartingState(workflow);
  int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow);
  ```
 ##### Get Defined Consumed-Events, Defined Produced-Events and their count
-```Java
+
+```java
  List<EventDefinition> consumedEvents = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);
  int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);
 
@@ -320,12 +340,14 @@ State startingState = WorkflowUtils.getStartingState(workflow);
  int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow);
  ```
 ##### Get Function definitions which is used by an action
-```Java
+
+```java
 FunctionDefinition finalizeApplicationFunctionDefinition =
         WorkflowUtils.getFunctionDefinitionsForAction(workflow, "finalizeApplicationAction");
 ```
 ##### Get Actions which uses a Function definition
-```Java
+
+```java
  List<Action> actionsForFunctionDefinition =
         WorkflowUtils.getActionsForFunctionDefinition(workflow, functionRefName);
 ```
\ No newline at end of file
diff --git a/api/pom.xml b/api/pom.xml
index b20aaf22..c2d52765 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -1,17 +1,14 @@
-<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">
+<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>io.serverlessworkflow</groupId>
         <artifactId>serverlessworkflow-parent</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
+        <version>4.3.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>serverlessworkflow-api</artifactId>
     <name>Serverless Workflow :: API</name>
-    <version>${project.parent.version}</version>
     <packaging>jar</packaging>
     <description>Java SDK for Serverless Workflow Specification</description>
 
@@ -27,31 +24,19 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-core</artifactId>
-            <version>[2.13.0,)</version>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>[2.13.0,)</version>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.dataformat</groupId>
             <artifactId>jackson-dataformat-yaml</artifactId>
-            <version>[2.13.0,)</version>
         </dependency>
         <dependency>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.github.erosb</groupId>
-            <artifactId>everit-json-schema</artifactId>
-        </dependency>
-
         <!-- test -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -98,12 +83,13 @@
                     <includeJsr303Annotations>true</includeJsr303Annotations>
                     <generateBuilders>true</generateBuilders>
                     <includeAdditionalProperties>false</includeAdditionalProperties>
+                    <useJakartaValidation>true</useJakartaValidation>
                     <includeToString>false</includeToString>
                     <includeHashcodeAndEquals>false</includeHashcodeAndEquals>
                     <includeConstructors>true</includeConstructors>
                     <constructorsRequiredPropertiesOnly>true</constructorsRequiredPropertiesOnly>
                     <serializable>true</serializable>
-                    <targetVersion>1.8</targetVersion>
+                    <targetVersion>${java.version}</targetVersion>
                     <usePrimitives>true</usePrimitives>
                 </configuration>
                 <executions>
@@ -122,13 +108,13 @@
                     <checkstyleRules>
                         <module name="Checker">
                             <module name="RegexpHeader">
-                                <property name="header" value="${checkstyle.header.template}"/>
-                                <property name="fileExtensions" value="${checkstyle.header.extensions}"/>
+                                <property name="header" value="${checkstyle.header.template}" />
+                                <property name="fileExtensions" value="${checkstyle.header.extensions}" />
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="@author"/>
-                                    <property name="message" value="No @author tag allowed"/>
+                                    <property name="format" value="@author" />
+                                    <property name="message" value="No @author tag allowed" />
                                 </module>
                             </module>
                         </module>
@@ -156,21 +142,12 @@
                 </executions>
             </plugin>
             <plugin>
-                <groupId>com.coveo</groupId>
-                <artifactId>fmt-maven-plugin</artifactId>
-                <configuration>
-                    <sourceDirectory>src/main/java</sourceDirectory>
-                    <testSourceDirectory>src/test/java</testSourceDirectory>
-                    <verbose>false</verbose>
-                    <filesNamePattern>.*\.java</filesNamePattern>
-                    <skip>false</skip>
-                    <skipSortingImports>false</skipSortingImports>
-                    <style>google</style>
-                </configuration>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
                         <goals>
-                            <goal>format</goal>
+                            <goal>test-jar</goal>
                         </goals>
                     </execution>
                 </executions>
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java
new file mode 100644
index 00000000..aa078cb4
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed 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 io.serverlessworkflow.api.deserializers;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import io.serverlessworkflow.api.auth.AuthDefinition;
+import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
+import io.serverlessworkflow.api.utils.Utils;
+import io.serverlessworkflow.api.workflow.Auth;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuthDeserializer extends StdDeserializer<Auth> {
+
+  private static final long serialVersionUID = 520L;
+  private static Logger logger = LoggerFactory.getLogger(AuthDeserializer.class);
+
+  @SuppressWarnings("unused")
+  private WorkflowPropertySource context;
+
+  public AuthDeserializer() {
+    this(Auth.class);
+  }
+
+  public AuthDeserializer(Class<?> vc) {
+    super(vc);
+  }
+
+  public AuthDeserializer(WorkflowPropertySource context) {
+    this(Auth.class);
+    this.context = context;
+  }
+
+  @Override
+  public Auth deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+
+    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
+    JsonNode node = jp.getCodec().readTree(jp);
+
+    Auth auth = new Auth();
+    List<AuthDefinition> authDefinitions = new ArrayList<>();
+
+    if (node.isArray()) {
+      for (final JsonNode nodeEle : node) {
+        authDefinitions.add(mapper.treeToValue(nodeEle, AuthDefinition.class));
+      }
+    } else {
+      String authFileDef = node.asText();
+      String authFileSrc = Utils.getResourceFileAsString(authFileDef);
+      if (authFileSrc != null && authFileSrc.trim().length() > 0) {
+        JsonNode authRefNode = Utils.getNode(authFileSrc);
+        JsonNode refAuth = authRefNode.get("auth");
+        if (refAuth != null) {
+          for (final JsonNode nodeEle : refAuth) {
+            authDefinitions.add(mapper.treeToValue(nodeEle, AuthDefinition.class));
+          }
+        } else {
+          logger.error("Unable to find auth definitions in reference file: {}", authFileSrc);
+        }
+
+      } else {
+        logger.error("Unable to load auth defs reference file: {}", authFileSrc);
+      }
+    }
+    auth.setAuthDefs(authDefinitions);
+    return auth;
+  }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java
index c3789b52..3859273c 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java
@@ -18,14 +18,11 @@
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.utils.Utils;
 import io.serverlessworkflow.api.workflow.Constants;
 import java.io.IOException;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,21 +60,8 @@ public Constants deserialize(JsonParser jp, DeserializationContext ctxt) throws
     } else {
       String constantsFileDef = node.asText();
       String constantsFileSrc = Utils.getResourceFileAsString(constantsFileDef);
-      JsonNode constantsRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (constantsFileSrc != null && constantsFileSrc.trim().length() > 0) {
-        // if its a yaml def convert to json first
-        if (!constantsFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(constantsFileSrc, Object.class);
-
-          constantsRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          constantsRefNode = jsonWriter.readTree(new JSONObject(constantsFileSrc).toString());
-        }
-
+        JsonNode constantsRefNode = Utils.getNode(constantsFileSrc);
         JsonNode refConstants = constantsRefNode.get("constants");
         if (refConstants != null) {
           constantsDefinition = refConstants;
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java
index beedc7dd..6fe366ea 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java
@@ -20,7 +20,6 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.error.ErrorDefinition;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.utils.Utils;
@@ -28,7 +27,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,21 +67,8 @@ public Errors deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE
     } else {
       String errorsFileDef = node.asText();
       String errorsFileSrc = Utils.getResourceFileAsString(errorsFileDef);
-      JsonNode errorsRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (errorsFileSrc != null && errorsFileSrc.trim().length() > 0) {
-        // if its a yaml def convert to json first
-        if (!errorsFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(errorsFileSrc, Object.class);
-
-          errorsRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          errorsRefNode = jsonWriter.readTree(new JSONObject(errorsFileSrc).toString());
-        }
-
+        JsonNode errorsRefNode = Utils.getNode(errorsFileSrc);
         JsonNode refErrors = errorsRefNode.get("errors");
         if (refErrors != null) {
           for (final JsonNode nodeEle : refErrors) {
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java
index cfa207df..a02fdf4b 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java
@@ -20,7 +20,6 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.events.EventDefinition;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.utils.Utils;
@@ -28,7 +27,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,21 +67,9 @@ public Events deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE
     } else {
       String eventsFileDef = node.asText();
       String eventsFileSrc = Utils.getResourceFileAsString(eventsFileDef);
-      JsonNode eventsRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (eventsFileSrc != null && eventsFileSrc.trim().length() > 0) {
         // if its a yaml def convert to json first
-        if (!eventsFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(eventsFileSrc, Object.class);
-
-          eventsRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          eventsRefNode = jsonWriter.readTree(new JSONObject(eventsFileSrc).toString());
-        }
-
+        JsonNode eventsRefNode = Utils.getNode(eventsFileSrc);
         JsonNode refEvents = eventsRefNode.get("events");
         if (refEvents != null) {
           for (final JsonNode nodeEle : refEvents) {
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java
index c27e2c48..b706b2d3 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java
@@ -20,7 +20,6 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.functions.FunctionDefinition;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.utils.Utils;
@@ -28,7 +27,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,20 +67,9 @@ public Functions deserialize(JsonParser jp, DeserializationContext ctxt) throws
       String functionsFileDef = node.asText();
       String functionsFileSrc = Utils.getResourceFileAsString(functionsFileDef);
       JsonNode functionsRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (functionsFileSrc != null && functionsFileSrc.trim().length() > 0) {
         // if its a yaml def convert to json first
-        if (!functionsFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(functionsFileSrc, Object.class);
-
-          functionsRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          functionsRefNode = jsonWriter.readTree(new JSONObject(functionsFileSrc).toString());
-        }
-
+        functionsRefNode = Utils.getNode(functionsFileSrc);
         JsonNode refFunctions = functionsRefNode.get("functions");
         if (refFunctions != null) {
           for (final JsonNode nodeEle : refFunctions) {
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java
index ff2fe44d..9eb47b5f 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java
@@ -20,7 +20,6 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.retry.RetryDefinition;
 import io.serverlessworkflow.api.utils.Utils;
@@ -28,7 +27,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,21 +67,8 @@ public Retries deserialize(JsonParser jp, DeserializationContext ctxt) throws IO
     } else {
       String retriesFileDef = node.asText();
       String retriesFileSrc = Utils.getResourceFileAsString(retriesFileDef);
-      JsonNode retriesRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (retriesFileSrc != null && retriesFileSrc.trim().length() > 0) {
-        // if its a yaml def convert to json first
-        if (!retriesFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(retriesFileSrc, Object.class);
-
-          retriesRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          retriesRefNode = jsonWriter.readTree(new JSONObject(retriesFileSrc).toString());
-        }
-
+        JsonNode retriesRefNode = Utils.getNode(retriesFileSrc);
         JsonNode refRetries = retriesRefNode.get("retries");
         if (refRetries != null) {
           for (final JsonNode nodeEle : refRetries) {
diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java
index e9ec05e7..60cc2a82 100644
--- a/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java
@@ -18,16 +18,13 @@
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
 import io.serverlessworkflow.api.utils.Utils;
 import io.serverlessworkflow.api.workflow.Secrets;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -66,21 +63,9 @@ public Secrets deserialize(JsonParser jp, DeserializationContext ctxt) throws IO
     } else {
       String secretsFileDef = node.asText();
       String secretsFileSrc = Utils.getResourceFileAsString(secretsFileDef);
-      JsonNode secretsRefNode;
-      ObjectMapper jsonWriter = new ObjectMapper();
       if (secretsFileSrc != null && secretsFileSrc.trim().length() > 0) {
         // if its a yaml def convert to json first
-        if (!secretsFileSrc.trim().startsWith("{")) {
-          // convert yaml to json to validate
-          ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-          Object obj = yamlReader.readValue(secretsFileSrc, Object.class);
-
-          secretsRefNode =
-              jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
-        } else {
-          secretsRefNode = jsonWriter.readTree(new JSONObject(secretsFileSrc).toString());
-        }
-
+        JsonNode secretsRefNode = Utils.getNode(secretsFileSrc);
         JsonNode refSecrets = secretsRefNode.get("secrets");
         if (refSecrets != null) {
           for (final JsonNode nodeEle : refSecrets) {
diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java b/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java
index 6bba312c..0c62d4d2 100644
--- a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java
+++ b/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java
@@ -22,6 +22,8 @@ public interface WorkflowDiagram {
 
   WorkflowDiagram setSource(String source);
 
+  WorkflowDiagram setTemplate(String template);
+
   String getSvgDiagram() throws Exception;
 
   WorkflowDiagram showLegend(boolean showLegend);
diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java
new file mode 100644
index 00000000..eb34b0eb
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed 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 io.serverlessworkflow.api.mapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonObjectMapperFactory {
+
+  private static final ObjectMapper instance = new JsonObjectMapper();
+
+  public static final ObjectMapper mapper() {
+    return instance;
+  }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java b/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java
index 1486d32a..5b42598d 100644
--- a/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java
+++ b/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java
@@ -42,11 +42,11 @@
 
 public class WorkflowModule extends SimpleModule {
 
-  private static final long serialVersionUID = 510l;
+  private static final long serialVersionUID = 510L;
 
-  private WorkflowPropertySource workflowPropertySource;
-  private ExtensionSerializer extensionSerializer;
-  private ExtensionDeserializer extensionDeserializer;
+  private final WorkflowPropertySource workflowPropertySource;
+  private final ExtensionSerializer extensionSerializer;
+  private final ExtensionDeserializer extensionDeserializer;
 
   public WorkflowModule() {
     this(null);
@@ -121,6 +121,7 @@ private void addDefaultDeserializers() {
         StateExecTimeout.class, new StateExecTimeoutDeserializer(workflowPropertySource));
     addDeserializer(Errors.class, new ErrorsDeserializer(workflowPropertySource));
     addDeserializer(ContinueAs.class, new ContinueAsDeserializer(workflowPropertySource));
+    addDeserializer(Auth.class, new AuthDeserializer(workflowPropertySource));
   }
 
   public ExtensionSerializer getExtensionSerializer() {
diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java
new file mode 100644
index 00000000..04371db4
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed 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 io.serverlessworkflow.api.mapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class YamlObjectMapperFactory {
+
+  private static final ObjectMapper instance = new YamlObjectMapper();
+
+  public static final ObjectMapper mapper() {
+    return instance;
+  }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java b/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java
deleted file mode 100644
index a4da2387..00000000
--- a/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2020-Present The Serverless Workflow Specification Authors
- *
- * Licensed 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 io.serverlessworkflow.api.schemaclient;
-
-import java.io.InputStream;
-import java.util.Objects;
-import org.everit.json.schema.loader.SchemaClient;
-
-public class ResourceSchemaClient implements SchemaClient {
-
-  @SuppressWarnings("unused")
-  private final SchemaClient fallbackClient;
-
-  private final String baseResourcePath = "/schema/";
-
-  public ResourceSchemaClient(SchemaClient fallbackClient) {
-    this.fallbackClient = Objects.requireNonNull(fallbackClient, "fallbackClient cannot be null");
-  }
-
-  @Override
-  public InputStream get(String path) {
-    path = path.substring("https://wg-serverless.org/".length());
-    return this.getClass().getResourceAsStream(baseResourcePath + path);
-  }
-}
diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java
index cf5ce81f..71f61606 100644
--- a/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java
@@ -41,12 +41,12 @@ public void serialize(FunctionRef functionRef, JsonGenerator gen, SerializerProv
           && (functionRef.getInvoke() == null
               || functionRef.getInvoke().equals(FunctionRef.Invoke.SYNC))
           && functionRef.getRefName() != null
-          && functionRef.getRefName().length() > 0) {
+          && !functionRef.getRefName().isEmpty()) {
         gen.writeString(functionRef.getRefName());
       } else {
         gen.writeStartObject();
 
-        if (functionRef.getRefName() != null && functionRef.getRefName().length() > 0) {
+        if (functionRef.getRefName() != null && !functionRef.getRefName().isEmpty()) {
           gen.writeStringField("refName", functionRef.getRefName());
         }
 
@@ -54,7 +54,7 @@ public void serialize(FunctionRef functionRef, JsonGenerator gen, SerializerProv
           gen.writeObjectField("arguments", functionRef.getArguments());
         }
 
-        if (functionRef.getSelectionSet() != null && functionRef.getSelectionSet().length() > 0) {
+        if (functionRef.getSelectionSet() != null && !functionRef.getSelectionSet().isEmpty()) {
           gen.writeStringField("selectionSet", functionRef.getSelectionSet());
         }
 
diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java
index b45871fa..b5ac7cbb 100644
--- a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java
+++ b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java
@@ -15,198 +15,171 @@
  */
 package io.serverlessworkflow.api.serializers;
 
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.List;
+import java.util.UUID;
+
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 import io.serverlessworkflow.api.Workflow;
-import io.serverlessworkflow.api.error.ErrorDefinition;
-import io.serverlessworkflow.api.events.EventDefinition;
-import io.serverlessworkflow.api.functions.FunctionDefinition;
 import io.serverlessworkflow.api.interfaces.Extension;
 import io.serverlessworkflow.api.interfaces.State;
-import io.serverlessworkflow.api.retry.RetryDefinition;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.util.UUID;
 
 public class WorkflowSerializer extends StdSerializer<Workflow> {
 
-  public WorkflowSerializer() {
-    this(Workflow.class);
-  }
-
-  protected WorkflowSerializer(Class<Workflow> t) {
-    super(t);
-  }
-
-  private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
-
-  @Override
-  public void serialize(Workflow workflow, JsonGenerator gen, SerializerProvider provider)
-      throws IOException {
-
-    gen.writeStartObject();
-
-    if (workflow.getId() != null && !workflow.getId().isEmpty()) {
-      gen.writeStringField("id", workflow.getId());
-    } else {
-      gen.writeStringField("id", generateUniqueId());
-    }
-
-    gen.writeStringField("name", workflow.getName());
-
-    if (workflow.getDescription() != null && !workflow.getDescription().isEmpty()) {
-      gen.writeStringField("description", workflow.getDescription());
-    }
-
-    if (workflow.getVersion() != null && !workflow.getVersion().isEmpty()) {
-      gen.writeStringField("version", workflow.getVersion());
-    }
-
-    if (workflow.getDataInputSchema() != null) {
-      if (workflow.getDataInputSchema().getSchema() != null
-          && workflow.getDataInputSchema().getSchema().length() > 0
-          && workflow.getDataInputSchema().isFailOnValidationErrors()) {
-        gen.writeStringField("dataInputSchema", workflow.getDataInputSchema().getSchema());
-
-      } else if (workflow.getDataInputSchema().getSchema() != null
-          && workflow.getDataInputSchema().getSchema().length() > 0
-          && !workflow.getDataInputSchema().isFailOnValidationErrors()) {
-        gen.writeObjectField("dataInputSchema", workflow.getDataInputSchema());
-      }
-    }
-
-    if (workflow.getStart() != null) {
-      gen.writeObjectField("start", workflow.getStart());
-    }
-
-    if (workflow.getSpecVersion() != null && !workflow.getSpecVersion().isEmpty()) {
-      gen.writeStringField("specVersion", workflow.getSpecVersion());
-    }
-
-    if (workflow.getExtensions() != null && !workflow.getExpressionLang().isEmpty()) {
-      gen.writeStringField("expressionLang", workflow.getExpressionLang());
-    }
-
-    if (workflow.isKeepActive()) {
-      gen.writeBooleanField("keepActive", workflow.isKeepActive());
-    }
-
-    if (workflow.isAutoRetries()) {
-      gen.writeBooleanField("autoRetries", workflow.isAutoRetries());
-    }
-
-    if (workflow.getMetadata() != null && !workflow.getMetadata().isEmpty()) {
-      gen.writeObjectField("metadata", workflow.getMetadata());
-    }
-
-    if (workflow.getEvents() != null && !workflow.getEvents().getEventDefs().isEmpty()) {
-      gen.writeArrayFieldStart("events");
-      for (EventDefinition eventDefinition : workflow.getEvents().getEventDefs()) {
-        gen.writeObject(eventDefinition);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("events");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getFunctions() != null && !workflow.getFunctions().getFunctionDefs().isEmpty()) {
-      gen.writeArrayFieldStart("functions");
-      for (FunctionDefinition function : workflow.getFunctions().getFunctionDefs()) {
-        gen.writeObject(function);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("functions");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getRetries() != null && !workflow.getRetries().getRetryDefs().isEmpty()) {
-      gen.writeArrayFieldStart("retries");
-      for (RetryDefinition retry : workflow.getRetries().getRetryDefs()) {
-        gen.writeObject(retry);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("retries");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getErrors() != null && !workflow.getErrors().getErrorDefs().isEmpty()) {
-      gen.writeArrayFieldStart("errors");
-      for (ErrorDefinition error : workflow.getErrors().getErrorDefs()) {
-        gen.writeObject(error);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("errors");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getSecrets() != null && !workflow.getSecrets().getSecretDefs().isEmpty()) {
-      gen.writeArrayFieldStart("secrets");
-      for (String secretDef : workflow.getSecrets().getSecretDefs()) {
-        gen.writeString(secretDef);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("secrets");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getConstants() != null && !workflow.getConstants().getConstantsDef().isEmpty()) {
-      gen.writeObjectField("constants", workflow.getConstants().getConstantsDef());
-    }
-
-    if (workflow.getTimeouts() != null) {
-      gen.writeObjectField("timeouts", workflow.getTimeouts());
-    }
-
-    if (workflow.getAuth() != null) {
-      gen.writeObjectField("auth", workflow.getAuth());
-    }
-
-    if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
-      gen.writeArrayFieldStart("states");
-      for (State state : workflow.getStates()) {
-        gen.writeObject(state);
-      }
-      gen.writeEndArray();
-    } else {
-      gen.writeArrayFieldStart("states");
-      gen.writeEndArray();
-    }
-
-    if (workflow.getExtensions() != null && !workflow.getExtensions().isEmpty()) {
-      gen.writeArrayFieldStart("extensions");
-      for (Extension extension : workflow.getExtensions()) {
-        gen.writeObject(extension);
-      }
-      gen.writeEndArray();
-    }
-
-    gen.writeEndObject();
-  }
-
-  protected static String generateUniqueId() {
-    try {
-      MessageDigest salt = MessageDigest.getInstance("SHA-256");
-
-      salt.update(UUID.randomUUID().toString().getBytes("UTF-8"));
-      return bytesToHex(salt.digest());
-    } catch (Exception e) {
-      return UUID.randomUUID().toString();
-    }
-  }
-
-  protected static String bytesToHex(byte[] bytes) {
-    char[] hexChars = new char[bytes.length * 2];
-    for (int j = 0; j < bytes.length; j++) {
-      int v = bytes[j] & 0xFF;
-      hexChars[j * 2] = hexArray[v >>> 4];
-      hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+    private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+    public WorkflowSerializer() {
+        this(Workflow.class);
+    }
+
+    protected WorkflowSerializer(Class<Workflow> t) {
+        super(t);
+    }
+
+    protected static String generateUniqueId() {
+        try {
+            MessageDigest salt = MessageDigest.getInstance("SHA-256");
+            salt.update(UUID.randomUUID().toString().getBytes("UTF-8"));
+            return bytesToHex(salt.digest());
+        } catch (Exception e) {
+            return UUID.randomUUID().toString();
+        }
+    }
+
+    protected static String bytesToHex(byte[] bytes) {
+        char[] hexChars = new char[bytes.length * 2];
+        for (int j = 0; j < bytes.length; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2] = hexArray[v >>> 4];
+            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+        }
+        return new String(hexChars);
+    }
+
+    // Helper to write either an array of items or a single string reference
+    private <T> void writeListOrRef(
+            JsonGenerator gen,
+            SerializerProvider prov,
+            String fieldName,
+            List<T> items,
+            String refValue) throws IOException {
+        if (items != null && !items.isEmpty()) {
+            gen.writeArrayFieldStart(fieldName);
+            for (T item : items) {
+                prov.defaultSerializeValue(item, gen);
+            }
+            gen.writeEndArray();
+        } else if (refValue != null) {
+            gen.writeStringField(fieldName, refValue);
+        }
+    }
+
+    @Override
+    public void serialize(Workflow workflow, JsonGenerator gen, SerializerProvider provider)
+            throws IOException {
+
+        gen.writeStartObject();
+
+        // --- ID / key / basic fields ---
+        if (workflow.getId() != null && !workflow.getId().isEmpty()) {
+            gen.writeStringField("id", workflow.getId());
+        } else {
+            gen.writeStringField("id", generateUniqueId());
+        }
+        if (workflow.getKey() != null) {
+            gen.writeStringField("key", workflow.getKey());
+        }
+        gen.writeStringField("name", workflow.getName());
+        if (workflow.getDescription() != null && !workflow.getDescription().isEmpty()) {
+            gen.writeStringField("description", workflow.getDescription());
+        }
+        if (workflow.getVersion() != null && !workflow.getVersion().isEmpty()) {
+            gen.writeStringField("version", workflow.getVersion());
+        }
+        if (workflow.getAnnotations() != null && !workflow.getAnnotations().isEmpty()) {
+            gen.writeObjectField("annotations", workflow.getAnnotations());
+        }
+        if (workflow.getDataInputSchema() != null) {
+            if (workflow.getDataInputSchema().getSchema() != null
+                    && !workflow.getDataInputSchema().getSchema().isEmpty()
+                    && workflow.getDataInputSchema().isFailOnValidationErrors()) {
+                gen.writeStringField("dataInputSchema", workflow.getDataInputSchema().getSchema());
+            } else if (workflow.getDataInputSchema().getSchema() != null
+                    && !workflow.getDataInputSchema().getSchema().isEmpty()
+                    && !workflow.getDataInputSchema().isFailOnValidationErrors()) {
+                gen.writeObjectField("dataInputSchema", workflow.getDataInputSchema());
+            }
+        }
+        if (workflow.getStart() != null) {
+            gen.writeObjectField("start", workflow.getStart());
+        }
+        if (workflow.getSpecVersion() != null && !workflow.getSpecVersion().isEmpty()) {
+            gen.writeStringField("specVersion", workflow.getSpecVersion());
+        }
+        if (workflow.getExpressionLang() != null && !workflow.getExpressionLang().isEmpty()) {
+            gen.writeStringField("expressionLang", workflow.getExpressionLang());
+        }
+        if (workflow.isKeepActive()) {
+            gen.writeBooleanField("keepActive", workflow.isKeepActive());
+        }
+        if (workflow.isAutoRetries()) {
+            gen.writeBooleanField("autoRetries", workflow.isAutoRetries());
+        }
+        if (workflow.getMetadata() != null && !workflow.getMetadata().isEmpty()) {
+            gen.writeObjectField("metadata", workflow.getMetadata());
+        }
+
+        // --- Collections or references ---
+        if (workflow.getEvents() != null) {
+            writeListOrRef(gen, provider,
+                    "events",
+                    workflow.getEvents().getEventDefs(),
+                    workflow.getEvents().getRefValue());
+        }
+        if (workflow.getFunctions() != null) {
+            writeListOrRef(gen, provider,
+                    "functions",
+                    workflow.getFunctions().getFunctionDefs(),
+                    workflow.getFunctions().getRefValue());
+        }
+        if (workflow.getRetries() != null) {
+            writeListOrRef(gen, provider,
+                    "retries",
+                    workflow.getRetries().getRetryDefs(),
+                    workflow.getRetries().getRefValue());
+        }
+        if (workflow.getErrors() != null) {
+            writeListOrRef(gen, provider,
+                    "errors",
+                    workflow.getErrors().getErrorDefs(),
+                    workflow.getErrors().getRefValue());
+        }
+        if (workflow.getSecrets() != null) {
+            writeListOrRef(gen, provider,
+                    "secrets",
+                    workflow.getSecrets().getSecretDefs(),
+                    workflow.getSecrets().getRefValue());
+        }
+
+        // --- Always-array fields ---
+        if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
+            gen.writeArrayFieldStart("states");
+            for (State state : workflow.getStates()) {
+                gen.writeObject(state);
+            }
+            gen.writeEndArray();
+        }
+        if (workflow.getExtensions() != null && !workflow.getExtensions().isEmpty()) {
+            gen.writeArrayFieldStart("extensions");
+            for (Extension ext : workflow.getExtensions()) {
+                gen.writeObject(ext);
+            }
+            gen.writeEndArray();
+        }
+
+        gen.writeEndObject();
     }
-    return new String(hexChars);
-  }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java b/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java
index 3e4b4274..9bdce416 100644
--- a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java
+++ b/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java
@@ -15,6 +15,11 @@
  */
 package io.serverlessworkflow.api.utils;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.serverlessworkflow.api.mapper.JsonObjectMapperFactory;
+import io.serverlessworkflow.api.mapper.YamlObjectMapperFactory;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -34,4 +39,14 @@ public static String getResourceFileAsString(String fileName) throws IOException
       }
     }
   }
+
+  public static ObjectMapper getObjectMapper(String source) {
+    return !source.trim().startsWith("{")
+        ? YamlObjectMapperFactory.mapper()
+        : JsonObjectMapperFactory.mapper();
+  }
+
+  public static JsonNode getNode(String source) throws JsonProcessingException {
+    return getObjectMapper(source).readTree(source);
+  }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java b/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java
index 830bb50a..847380fb 100644
--- a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java
+++ b/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java
@@ -15,26 +15,23 @@
  */
 package io.serverlessworkflow.api.validation;
 
-import io.serverlessworkflow.api.schemaclient.ResourceSchemaClient;
-import org.everit.json.schema.Schema;
-import org.everit.json.schema.loader.SchemaLoader;
-import org.everit.json.schema.loader.internal.DefaultSchemaClient;
-import org.json.JSONObject;
-import org.json.JSONTokener;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 public class WorkflowSchemaLoader {
-  private static final JSONObject workflowSchema =
-      new JSONObject(
-          new JSONTokener(WorkflowSchemaLoader.class.getResourceAsStream("/schema/workflow.json")));
 
-  public static Schema getWorkflowSchema() {
-    SchemaLoader schemaLoader =
-        SchemaLoader.builder()
-            .schemaClient(new ResourceSchemaClient(new DefaultSchemaClient()))
-            .schemaJson(workflowSchema)
-            .resolutionScope("classpath:schema")
-            .draftV7Support()
-            .build();
-    return schemaLoader.load().build();
+  public static JsonNode getWorkflowSchema() {
+    try {
+      return ObjectMapperHolder.objectMapper.readTree(
+          WorkflowSchemaLoader.class.getResourceAsStream("/schema/workflow.json"));
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private static class ObjectMapperHolder {
+    public static final ObjectMapper objectMapper = new ObjectMapper();
   }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Auth.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Auth.java
new file mode 100644
index 00000000..e7828370
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Auth.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed 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 io.serverlessworkflow.api.workflow;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import io.serverlessworkflow.api.auth.AuthDefinition;
+
+public class Auth {
+    private String refValue;
+    private List<AuthDefinition> authDefs;
+
+    public Auth() {
+    }
+
+    public Auth(AuthDefinition authDef) {
+        this.authDefs = new ArrayList<>();
+        this.authDefs.add(authDef);
+    }
+
+    public Auth(List<AuthDefinition> authDefs) {
+        this.authDefs = authDefs;
+    }
+
+    public Auth(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<AuthDefinition> getAuthDefs() {
+        if (authDefs == null) {
+            return Collections.emptyList();
+        }
+        return authDefs;
+    }
+
+    public void setAuthDefs(List<AuthDefinition> authDefs) {
+        this.authDefs = authDefs;
+    }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java b/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java
index 61692caf..e3fc3d55 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java
@@ -15,6 +15,9 @@
  */
 package io.serverlessworkflow.api.workflow;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
@@ -23,53 +26,53 @@
 import io.serverlessworkflow.api.Workflow;
 import io.serverlessworkflow.api.mapper.JsonObjectMapper;
 import io.serverlessworkflow.api.mapper.YamlObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-/** Base Workflow provides some extra functionality for the Workflow types */
+/**
+ * Base Workflow provides some extra functionality for the Workflow types
+ */
 public class BaseWorkflow {
 
-  private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper();
-  private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper();
+    private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper();
+    private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper();
 
-  private static Logger logger = LoggerFactory.getLogger(BaseWorkflow.class);
+    private static Logger logger = LoggerFactory.getLogger(BaseWorkflow.class);
 
-  public static Workflow fromSource(String source) {
-    // try it as json markup first, if fails try yaml
-    try {
-      return jsonObjectMapper.readValue(source, Workflow.class);
-    } catch (Exception e) {
-      logger.info("Unable to convert as json markup, trying as yaml");
-      try {
-        return yamlObjectMapper.readValue(source, Workflow.class);
-      } catch (Exception ee) {
-        throw new IllegalArgumentException(
-            "Could not convert markup to Workflow: " + ee.getMessage());
-      }
+    public static Workflow fromSource(String source) {
+        // try it as json markup first, if fails try yaml
+        try {
+            return jsonObjectMapper.readValue(source, Workflow.class);
+        } catch (Exception e) {
+            logger.info("Unable to convert as json markup, trying as yaml");
+            try {
+                return yamlObjectMapper.readValue(source, Workflow.class);
+            } catch (Exception ee) {
+                throw new IllegalArgumentException(
+                        "Could not convert markup to Workflow: " + ee.getMessage());
+            }
+        }
     }
-  }
 
-  public static String toJson(Workflow workflow) {
-    try {
-      return jsonObjectMapper.writeValueAsString(workflow);
-    } catch (JsonProcessingException e) {
-      logger.error("Error mapping to json: " + e.getMessage());
-      return null;
+    public static String toJson(Workflow workflow) {
+        try {
+            return jsonObjectMapper.writeValueAsString(workflow);
+        } catch (JsonProcessingException e) {
+            logger.error("Error mapping to json: {}", e.getMessage());
+            throw new IllegalArgumentException("Could not convert workflow to json: " + e.getMessage());
+        }
     }
-  }
 
-  public static String toYaml(Workflow workflow) {
-    try {
-      String jsonString = jsonObjectMapper.writeValueAsString(workflow);
-      JsonNode jsonNode = jsonObjectMapper.readTree(jsonString);
-      YAMLFactory yamlFactory =
-          new YAMLFactory()
-              .disable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
-              .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
-      return new YAMLMapper(yamlFactory).writeValueAsString(jsonNode);
-    } catch (Exception e) {
-      logger.error("Error mapping to yaml: " + e.getMessage());
-      return null;
+    public static String toYaml(Workflow workflow) {
+        try {
+            String jsonString = jsonObjectMapper.writeValueAsString(workflow);
+            JsonNode jsonNode = jsonObjectMapper.readTree(jsonString);
+            YAMLFactory yamlFactory =
+                    new YAMLFactory()
+                            .disable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
+                            .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
+            return new YAMLMapper(yamlFactory).writeValueAsString(jsonNode);
+        } catch (Exception e) {
+            logger.error("Error mapping to yaml: {}", e.getMessage());
+            throw new IllegalArgumentException("Could not convert workflow to yaml: " + e.getMessage());
+        }
     }
-  }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java
index 8431b94a..9e474139 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java
@@ -15,36 +15,42 @@
  */
 package io.serverlessworkflow.api.workflow;
 
-import io.serverlessworkflow.api.error.ErrorDefinition;
+import java.util.Collections;
 import java.util.List;
 
-public class Errors {
-  private String refValue;
-  private List<ErrorDefinition> errorDefs;
-
-  public Errors() {}
-
-  public Errors(List<ErrorDefinition> errorDefs) {
-    this.errorDefs = errorDefs;
-  }
-
-  public Errors(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public String getRefValue() {
-    return refValue;
-  }
-
-  public void setRefValue(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public List<ErrorDefinition> getErrorDefs() {
-    return errorDefs;
-  }
+import io.serverlessworkflow.api.error.ErrorDefinition;
 
-  public void setErrorDefs(List<ErrorDefinition> errorDefs) {
-    this.errorDefs = errorDefs;
-  }
+public class Errors {
+    private String refValue;
+    private List<ErrorDefinition> errorDefs;
+
+    public Errors() {
+    }
+
+    public Errors(List<ErrorDefinition> errorDefs) {
+        this.errorDefs = errorDefs;
+    }
+
+    public Errors(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<ErrorDefinition> getErrorDefs() {
+        if (errorDefs == null) {
+            return Collections.emptyList();
+        }
+        return errorDefs;
+    }
+
+    public void setErrorDefs(List<ErrorDefinition> errorDefs) {
+        this.errorDefs = errorDefs;
+    }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java
index 24080e51..59af2c3f 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java
@@ -15,36 +15,42 @@
  */
 package io.serverlessworkflow.api.workflow;
 
-import io.serverlessworkflow.api.events.EventDefinition;
+import java.util.Collections;
 import java.util.List;
 
-public class Events {
-  private String refValue;
-  private List<EventDefinition> eventDefs;
-
-  public Events() {}
-
-  public Events(List<EventDefinition> eventDefs) {
-    this.eventDefs = eventDefs;
-  }
-
-  public Events(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public String getRefValue() {
-    return refValue;
-  }
-
-  public void setRefValue(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public List<EventDefinition> getEventDefs() {
-    return eventDefs;
-  }
+import io.serverlessworkflow.api.events.EventDefinition;
 
-  public void setEventDefs(List<EventDefinition> eventDefs) {
-    this.eventDefs = eventDefs;
-  }
+public class Events {
+    private String refValue;
+    private List<EventDefinition> eventDefs;
+
+    public Events() {
+    }
+
+    public Events(List<EventDefinition> eventDefs) {
+        this.eventDefs = eventDefs;
+    }
+
+    public Events(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<EventDefinition> getEventDefs() {
+        if (eventDefs == null) {
+            return Collections.emptyList();
+        }
+        return eventDefs;
+    }
+
+    public void setEventDefs(List<EventDefinition> eventDefs) {
+        this.eventDefs = eventDefs;
+    }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java
index f269bc08..6e927f9b 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java
@@ -15,36 +15,42 @@
  */
 package io.serverlessworkflow.api.workflow;
 
-import io.serverlessworkflow.api.functions.FunctionDefinition;
+import java.util.Collections;
 import java.util.List;
 
-public class Functions {
-  private String refValue;
-  private List<FunctionDefinition> functionDefs;
-
-  public Functions() {}
-
-  public Functions(List<FunctionDefinition> functionDefs) {
-    this.functionDefs = functionDefs;
-  }
-
-  public Functions(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public String getRefValue() {
-    return refValue;
-  }
-
-  public void setRefValue(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public List<FunctionDefinition> getFunctionDefs() {
-    return functionDefs;
-  }
+import io.serverlessworkflow.api.functions.FunctionDefinition;
 
-  public void setFunctionDefs(List<FunctionDefinition> functionDefs) {
-    this.functionDefs = functionDefs;
-  }
+public class Functions {
+    private String refValue;
+    private List<FunctionDefinition> functionDefs;
+
+    public Functions() {
+    }
+
+    public Functions(List<FunctionDefinition> functionDefs) {
+        this.functionDefs = functionDefs;
+    }
+
+    public Functions(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<FunctionDefinition> getFunctionDefs() {
+        if (functionDefs == null) {
+            return Collections.emptyList();
+        }
+        return functionDefs;
+    }
+
+    public void setFunctionDefs(List<FunctionDefinition> functionDefs) {
+        this.functionDefs = functionDefs;
+    }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java
index af1ae1e0..fa539ac0 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java
@@ -15,36 +15,42 @@
  */
 package io.serverlessworkflow.api.workflow;
 
-import io.serverlessworkflow.api.retry.RetryDefinition;
+import java.util.Collections;
 import java.util.List;
 
-public class Retries {
-  private String refValue;
-  private List<RetryDefinition> retryDefs;
-
-  public Retries() {}
-
-  public Retries(List<RetryDefinition> retryDefs) {
-    this.retryDefs = retryDefs;
-  }
-
-  public Retries(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public String getRefValue() {
-    return refValue;
-  }
-
-  public void setRefValue(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public List<RetryDefinition> getRetryDefs() {
-    return retryDefs;
-  }
+import io.serverlessworkflow.api.retry.RetryDefinition;
 
-  public void setRetryDefs(List<RetryDefinition> retryDefs) {
-    this.retryDefs = retryDefs;
-  }
+public class Retries {
+    private String refValue;
+    private List<RetryDefinition> retryDefs;
+
+    public Retries() {
+    }
+
+    public Retries(List<RetryDefinition> retryDefs) {
+        this.retryDefs = retryDefs;
+    }
+
+    public Retries(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<RetryDefinition> getRetryDefs() {
+        if (retryDefs == null) {
+            return Collections.emptyList();
+        }
+        return retryDefs;
+    }
+
+    public void setRetryDefs(List<RetryDefinition> retryDefs) {
+        this.retryDefs = retryDefs;
+    }
 }
diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java
index 2dbb6b31..03edc0fd 100644
--- a/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java
+++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java
@@ -15,35 +15,40 @@
  */
 package io.serverlessworkflow.api.workflow;
 
+import java.util.Collections;
 import java.util.List;
 
 public class Secrets {
-  private String refValue;
-  private List<String> secretDefs;
-
-  public Secrets() {}
-
-  public Secrets(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public Secrets(List<String> secretDefs) {
-    this.secretDefs = secretDefs;
-  }
-
-  public String getRefValue() {
-    return refValue;
-  }
-
-  public void setRefValue(String refValue) {
-    this.refValue = refValue;
-  }
-
-  public List<String> getSecretDefs() {
-    return secretDefs;
-  }
-
-  public void setSecretDefs(List<String> secretDefs) {
-    this.secretDefs = secretDefs;
-  }
+    private String refValue;
+    private List<String> secretDefs;
+
+    public Secrets() {
+    }
+
+    public Secrets(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public Secrets(List<String> secretDefs) {
+        this.secretDefs = secretDefs;
+    }
+
+    public String getRefValue() {
+        return refValue;
+    }
+
+    public void setRefValue(String refValue) {
+        this.refValue = refValue;
+    }
+
+    public List<String> getSecretDefs() {
+        if (secretDefs == null) {
+            return Collections.emptyList();
+        }
+        return secretDefs;
+    }
+
+    public void setSecretDefs(List<String> secretDefs) {
+        this.secretDefs = secretDefs;
+    }
 }
diff --git a/api/src/main/resources/schema/events/eventdef.json b/api/src/main/resources/schema/events/eventdef.json
index 6670b856..a585f782 100644
--- a/api/src/main/resources/schema/events/eventdef.json
+++ b/api/src/main/resources/schema/events/eventdef.json
@@ -24,6 +24,11 @@
         "$ref": "../correlation/correlationdef.json"
       }
     },
+    "dataOnly": {
+      "type": "boolean",
+      "default": true,
+      "description": "If `true`, only the Event payload is accessible to consuming Workflow states. If `false`, both event payload and context attributes should be accessible "
+    },
     "kind": {
       "type" : "string",
       "enum": ["consumed", "produced"],
diff --git a/api/src/main/resources/schema/events/eventref.json b/api/src/main/resources/schema/events/eventref.json
index 76334993..c0e04a7a 100644
--- a/api/src/main/resources/schema/events/eventref.json
+++ b/api/src/main/resources/schema/events/eventref.json
@@ -16,7 +16,8 @@
       "description": "Maximum amount of time (ISO 8601 format) to wait for the result event. If not defined it should default to the actionExecutionTimeout"
     },
     "data": {
-      "type": "string",
+      "type": "object",
+      "existingJavaType": "com.fasterxml.jackson.databind.JsonNode",
       "description": "Expression which selects parts of the states data output to become the data of the produced event."
     },
     "contextAttributes": {
diff --git a/api/src/main/resources/schema/produce/produceevent.json b/api/src/main/resources/schema/produce/produceevent.json
index b5bb7d5f..fd3ecdb4 100644
--- a/api/src/main/resources/schema/produce/produceevent.json
+++ b/api/src/main/resources/schema/produce/produceevent.json
@@ -8,8 +8,14 @@
       "minLength": 1
     },
     "data": {
-      "type": "string",
-      "description": "Workflow expression which selects parts of the states data output to become the data of the produced event"
+      "type": "object",
+      "description": "Workflow expression which selects parts of the states data output to become the data of the produced event",
+      "existingJavaType": "com.fasterxml.jackson.databind.JsonNode"
+    },
+    "contextAttributes": {
+      "type": "object",
+      "description": "Add additional event extension context attributes",
+      "existingJavaType": "java.util.Map<String, String>"
     }
   },
   "required": [
diff --git a/api/src/main/resources/schema/workflow.json b/api/src/main/resources/schema/workflow.json
index 19164ca5..f8309b6c 100644
--- a/api/src/main/resources/schema/workflow.json
+++ b/api/src/main/resources/schema/workflow.json
@@ -1,5 +1,5 @@
 {
-  "$id": "https://wg-serverless.org/workflow.schema.json",
+  "$id": "classpath:schema/workflow.schema.json",
   "$schema": "http://json-schema.org/draft-07/schema#",
   "description": "Serverless Workflow is a vendor-neutral specification for defining the model of workflows responsible for orchestrating event-driven serverless applications.",
   "type": "object",
@@ -14,6 +14,10 @@
       "description": "Workflow unique identifier",
       "minLength": 1
     },
+    "key": {
+      "type": "string",
+      "description": "Workflow Domain-specific identifier"
+    },
     "name": {
       "type": "string",
       "description": "Workflow name",
@@ -27,6 +31,14 @@
       "type": "string",
       "description": "Workflow version"
     },
+    "annotations": {
+      "type": "array",
+      "description": "List of helpful terms describing the workflows intended purpose, subject areas, or other important qualities",
+      "minItems": 1,
+      "items": {
+        "type": "string"
+      }
+    },
     "dataInputSchema": {
       "$ref": "datainputschema/datainputschema.json",
       "description": "Workflow data input schema"
@@ -92,7 +104,9 @@
       "$ref": "timeouts/timeoutsdef.json"
     },
     "auth": {
-      "$ref": "auth/auth.json"
+      "type": "object",
+      "existingJavaType": "io.serverlessworkflow.api.workflow.Auth",
+      "description": "Workflow Auth definitions"
     },
     "states": {
       "type": "array",
@@ -145,10 +159,15 @@
       }
     }
   },
-  "required": [
-    "id",
-    "name",
-    "version",
-    "states"
-  ]
-}
\ No newline at end of file
+ "required": [
+        "id",
+        "name",
+        "version",
+        "states"
+ ],
+ "dependencies":
+  {
+    "id": { "not": { "required": ["key"] } },
+    "key": { "not": { "required": ["id"] } }
+  }
+}
diff --git a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java
index 2c6da083..f5b30d07 100644
--- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java
+++ b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java
@@ -25,6 +25,7 @@
 import io.serverlessworkflow.api.datainputschema.DataInputSchema;
 import io.serverlessworkflow.api.defaultdef.DefaultConditionDefinition;
 import io.serverlessworkflow.api.end.End;
+import io.serverlessworkflow.api.events.EventDefinition;
 import io.serverlessworkflow.api.events.EventRef;
 import io.serverlessworkflow.api.functions.FunctionDefinition;
 import io.serverlessworkflow.api.functions.FunctionRef;
@@ -39,9 +40,11 @@
 import io.serverlessworkflow.api.test.utils.WorkflowTestUtils;
 import io.serverlessworkflow.api.timeouts.WorkflowExecTimeout;
 import io.serverlessworkflow.api.workflow.Constants;
+import io.serverlessworkflow.api.workflow.Events;
 import io.serverlessworkflow.api.workflow.Retries;
 import io.serverlessworkflow.api.workflow.Secrets;
 import java.util.List;
+import java.util.Map;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
@@ -103,10 +106,11 @@ public void testSpecExamplesParsing(String workflowLocation) {
 
   @ParameterizedTest
   @ValueSource(strings = {"/features/applicantrequest.json", "/features/applicantrequest.yml"})
-  public void testSpecFreatureFunctionRef(String workflowLocation) {
+  public void testSpecFeatureFunctionRef(String workflowLocation) {
     Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
 
     assertNotNull(workflow);
+    assertNull(workflow.getKey());
     assertNotNull(workflow.getId());
     assertNotNull(workflow.getName());
     assertNotNull(workflow.getStates());
@@ -116,6 +120,46 @@ public void testSpecFreatureFunctionRef(String workflowLocation) {
     assertEquals(1, workflow.getFunctions().getFunctionDefs().size());
   }
 
+  @ParameterizedTest
+  @ValueSource(
+      strings = {
+        "/features/applicantrequest-with-key.json",
+        "/features/applicantrequest-with-key.yml"
+      })
+  public void testSpecFeatureFunctionRefWithKey(String workflowLocation) {
+    Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
+
+    assertNotNull(workflow);
+    assertEquals("applicant-key-request", workflow.getKey());
+    assertNull(workflow.getId());
+    assertNotNull(workflow.getName());
+    assertNotNull(workflow.getStates());
+    assertTrue(workflow.getStates().size() > 0);
+
+    assertNotNull(workflow.getFunctions());
+    assertEquals(1, workflow.getFunctions().getFunctionDefs().size());
+  }
+
+  @ParameterizedTest
+  @ValueSource(
+      strings = {
+        "/features/applicantrequest-with-id-and-key.json",
+        "/features/applicantrequest-with-id-and-key.yml"
+      })
+  public void testSpecFeatureFunctionRefWithIdAndKey(String workflowLocation) {
+    Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
+
+    assertNotNull(workflow);
+    assertEquals("applicant-key-request", workflow.getKey());
+    assertEquals("applicant-with-key-and-id", workflow.getId());
+    assertNotNull(workflow.getName());
+    assertNotNull(workflow.getStates());
+    assertTrue(workflow.getStates().size() > 0);
+
+    assertNotNull(workflow.getFunctions());
+    assertEquals(1, workflow.getFunctions().getFunctionDefs().size());
+  }
+
   @ParameterizedTest
   @ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"})
   public void testSpecFreatureEventRef(String workflowLocation) {
@@ -221,6 +265,12 @@ public void testTransitions(String workflowLocation) {
     assertEquals("RejectApplication", cond2.getTransition().getNextState());
     assertNotNull(cond2.getTransition().getProduceEvents());
     assertEquals(1, cond2.getTransition().getProduceEvents().size());
+    assertNotNull(cond2.getTransition().getProduceEvents().get(0).getContextAttributes());
+    Map<String, String> contextAttributes =
+        cond2.getTransition().getProduceEvents().get(0).getContextAttributes();
+    assertEquals(2, contextAttributes.size());
+    assertEquals("IN", contextAttributes.get("order_location"));
+    assertEquals("online", contextAttributes.get("order_type"));
     assertFalse(cond2.getTransition().isCompensate());
 
     assertNotNull(switchState.getDefaultCondition());
@@ -625,7 +675,7 @@ public void testAuthBasic(String workflowLocation) {
     assertNotNull(workflow.getName());
 
     assertNotNull(workflow.getAuth());
-    AuthDefinition auth = workflow.getAuth();
+    AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0);
     assertNotNull(auth.getName());
     assertEquals("authname", auth.getName());
     assertNotNull(auth.getScheme());
@@ -645,7 +695,7 @@ public void testAuthBearer(String workflowLocation) {
     assertNotNull(workflow.getName());
 
     assertNotNull(workflow.getAuth());
-    AuthDefinition auth = workflow.getAuth();
+    AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0);
     assertNotNull(auth.getName());
     assertEquals("authname", auth.getName());
     assertNotNull(auth.getScheme());
@@ -664,7 +714,7 @@ public void testAuthOAuth(String workflowLocation) {
     assertNotNull(workflow.getName());
 
     assertNotNull(workflow.getAuth());
-    AuthDefinition auth = workflow.getAuth();
+    AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0);
     assertNotNull(auth.getName());
     assertEquals("authname", auth.getName());
     assertNotNull(auth.getScheme());
@@ -838,4 +888,39 @@ public void testFunctionInvoke(String workflowLocation) {
     assertNotNull(action3.getEventRef().getInvoke());
     assertEquals(EventRef.Invoke.ASYNC, action3.getEventRef().getInvoke());
   }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"/features/annotations.json", "/features/annotations.yml"})
+  public void testAnnotations(String workflowLocation) {
+    Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
+
+    assertNotNull(workflow);
+    assertNotNull(workflow.getId());
+    assertNotNull(workflow.getName());
+
+    assertNotNull(workflow.getAnnotations());
+    List<String> annotations = workflow.getAnnotations();
+    assertEquals(4, annotations.size());
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"/features/eventdefdataonly.json", "/features/eventdefdataonly.yml"})
+  public void testEventDefDataOnly(String workflowLocation) {
+    Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
+
+    assertNotNull(workflow);
+    assertNotNull(workflow.getId());
+    assertNotNull(workflow.getName());
+
+    assertNotNull(workflow.getEvents());
+    Events events = workflow.getEvents();
+    assertNotNull(workflow.getEvents().getEventDefs());
+    assertEquals(2, events.getEventDefs().size());
+    EventDefinition eventDefOne = events.getEventDefs().get(0);
+    EventDefinition eventDefTwo = events.getEventDefs().get(1);
+    assertEquals("visaApprovedEvent", eventDefOne.getName());
+    assertFalse(eventDefOne.isDataOnly());
+    assertEquals("visaRejectedEvent", eventDefTwo.getName());
+    assertTrue(eventDefTwo.isDataOnly());
+  }
 }
diff --git a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java b/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java
index 514375e1..ff43f167 100644
--- a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java
+++ b/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java
@@ -16,7 +16,9 @@
 package io.serverlessworkflow.api.test;
 
 import static io.serverlessworkflow.api.states.DefaultState.Type.SLEEP;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.serverlessworkflow.api.Workflow;
 import io.serverlessworkflow.api.auth.AuthDefinition;
@@ -29,6 +31,7 @@
 import io.serverlessworkflow.api.schedule.Schedule;
 import io.serverlessworkflow.api.start.Start;
 import io.serverlessworkflow.api.states.SleepState;
+import io.serverlessworkflow.api.workflow.Auth;
 import io.serverlessworkflow.api.workflow.Events;
 import io.serverlessworkflow.api.workflow.Functions;
 import java.util.Arrays;
@@ -162,22 +165,24 @@ public void testAuth() {
             .withVersion("1.0")
             .withStart(new Start())
             .withAuth(
-                new AuthDefinition()
-                    .withName("authname")
-                    .withScheme(AuthDefinition.Scheme.BASIC)
-                    .withBasicauth(
-                        new BasicAuthDefinition()
-                            .withUsername("testuser")
-                            .withPassword("testPassword")));
+                new Auth(
+                    new AuthDefinition()
+                        .withName("authname")
+                        .withScheme(AuthDefinition.Scheme.BASIC)
+                        .withBasicauth(
+                            new BasicAuthDefinition()
+                                .withUsername("testuser")
+                                .withPassword("testPassword"))));
 
     assertNotNull(workflow);
     assertNotNull(workflow.getAuth());
-    assertNotNull(workflow.getAuth().getName());
-    assertEquals("authname", workflow.getAuth().getName());
-    assertNotNull(workflow.getAuth().getScheme());
-    assertEquals("basic", workflow.getAuth().getScheme().value());
-    assertNotNull(workflow.getAuth().getBasicauth());
-    assertEquals("testuser", workflow.getAuth().getBasicauth().getUsername());
-    assertEquals("testPassword", workflow.getAuth().getBasicauth().getPassword());
+    assertNotNull(workflow.getAuth().getAuthDefs().get(0));
+    assertEquals("authname", workflow.getAuth().getAuthDefs().get(0).getName());
+    assertNotNull(workflow.getAuth().getAuthDefs().get(0).getScheme());
+    assertEquals("basic", workflow.getAuth().getAuthDefs().get(0).getScheme().value());
+    assertNotNull(workflow.getAuth().getAuthDefs().get(0).getBasicauth());
+    assertEquals("testuser", workflow.getAuth().getAuthDefs().get(0).getBasicauth().getUsername());
+    assertEquals(
+        "testPassword", workflow.getAuth().getAuthDefs().get(0).getBasicauth().getPassword());
   }
 }
diff --git a/api/src/test/resources/examples/applicantrequest.json b/api/src/test/resources/examples/applicantrequest.json
index 1621c2bd..652e361b 100644
--- a/api/src/test/resources/examples/applicantrequest.json
+++ b/api/src/test/resources/examples/applicantrequest.json
@@ -1,5 +1,6 @@
 {
   "id": "applicantrequest",
+  "key": "applicant-key-request",
   "version": "1.0",
   "specVersion": "0.8",
   "name": "Applicant Request Decision Workflow",
diff --git a/api/src/test/resources/features/annotations.json b/api/src/test/resources/features/annotations.json
new file mode 100644
index 00000000..90ed6dd1
--- /dev/null
+++ b/api/src/test/resources/features/annotations.json
@@ -0,0 +1,6 @@
+{
+  "id" : "test-workflow",
+  "name" : "test-workflow-name",
+  "version" : "1.0",
+  "annotations": ["a", "b", "c", "d"]
+}
\ No newline at end of file
diff --git a/api/src/test/resources/features/annotations.yml b/api/src/test/resources/features/annotations.yml
new file mode 100644
index 00000000..54e359ae
--- /dev/null
+++ b/api/src/test/resources/features/annotations.yml
@@ -0,0 +1,8 @@
+id: test-workflow
+name: test-workflow-name
+version: '1.0'
+annotations:
+  - a
+  - b
+  - c
+  - d
diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.json b/api/src/test/resources/features/applicantrequest-with-id-and-key.json
new file mode 100644
index 00000000..405d7c36
--- /dev/null
+++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.json
@@ -0,0 +1,60 @@
+{
+  "id": "applicant-with-key-and-id",
+  "key": "applicant-key-request",
+  "version": "1.0",
+  "specVersion": "0.8",
+  "name": "Applicant Request Decision Workflow",
+  "description": "Determine if applicant request is valid",
+  "start": "CheckApplication",
+  "functions": [
+    {
+      "name": "sendRejectionEmailFunction",
+      "operation": "http://myapis.org/applicationapi.json#emailRejection"
+    }
+  ],
+  "states":[
+    {
+      "name":"CheckApplication",
+      "type":"switch",
+      "dataConditions": [
+        {
+          "condition": "${ .applicants | .age >= 18 }",
+          "transition": "StartApplication"
+        },
+        {
+          "condition": "${ .applicants | .age < 18 }",
+          "transition": "RejectApplication"
+        }
+      ],
+      "defaultCondition": {
+        "transition": "RejectApplication"
+      }
+    },
+    {
+      "name": "StartApplication",
+      "type": "operation",
+      "actions": [
+        {
+          "subFlowRef": "startApplicationWorkflowId"
+        }
+      ],
+      "end": true
+    },
+    {
+      "name":"RejectApplication",
+      "type":"operation",
+      "actionMode":"sequential",
+      "actions":[
+        {
+          "functionRef": {
+            "refName": "sendRejectionEmailFunction",
+            "arguments": {
+              "applicant": "${ .applicant }"
+            }
+          }
+        }
+      ],
+      "end": true
+    }
+  ]
+}
\ No newline at end of file
diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.yml b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml
new file mode 100644
index 00000000..8a123663
--- /dev/null
+++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml
@@ -0,0 +1,34 @@
+id: applicant-with-key-and-id
+key: applicant-key-request
+version: '1.0'
+specVersion: '0.8'
+name: Applicant Request Decision Workflow
+description: Determine if applicant request is valid
+start: CheckApplication
+functions:
+  - name: sendRejectionEmailFunction
+    operation: http://myapis.org/applicationapi.json#emailRejection
+states:
+  - name: CheckApplication
+    type: switch
+    dataConditions:
+      - condition: "${ .applicants | .age >= 18 }"
+        transition: StartApplication
+      - condition: "${ .applicants | .age < 18 }"
+        transition: RejectApplication
+    defaultCondition:
+      transition: RejectApplication
+  - name: StartApplication
+    type: operation
+    actions:
+      - subFlowRef: startApplicationWorkflowId
+    end: true
+  - name: RejectApplication
+    type: operation
+    actionMode: sequential
+    actions:
+      - functionRef:
+          refName: sendRejectionEmailFunction
+          arguments:
+            applicant: "${ .applicant }"
+    end: true
diff --git a/api/src/test/resources/features/applicantrequest-with-key.json b/api/src/test/resources/features/applicantrequest-with-key.json
new file mode 100644
index 00000000..f0481b00
--- /dev/null
+++ b/api/src/test/resources/features/applicantrequest-with-key.json
@@ -0,0 +1,59 @@
+{
+  "key": "applicant-key-request",
+  "version": "1.0",
+  "specVersion": "0.8",
+  "name": "Applicant Request Decision Workflow",
+  "description": "Determine if applicant request is valid",
+  "start": "CheckApplication",
+  "functions": [
+    {
+      "name": "sendRejectionEmailFunction",
+      "operation": "http://myapis.org/applicationapi.json#emailRejection"
+    }
+  ],
+  "states":[
+    {
+      "name":"CheckApplication",
+      "type":"switch",
+      "dataConditions": [
+        {
+          "condition": "${ .applicants | .age >= 18 }",
+          "transition": "StartApplication"
+        },
+        {
+          "condition": "${ .applicants | .age < 18 }",
+          "transition": "RejectApplication"
+        }
+      ],
+      "defaultCondition": {
+        "transition": "RejectApplication"
+      }
+    },
+    {
+      "name": "StartApplication",
+      "type": "operation",
+      "actions": [
+        {
+          "subFlowRef": "startApplicationWorkflowId"
+        }
+      ],
+      "end": true
+    },
+    {
+      "name":"RejectApplication",
+      "type":"operation",
+      "actionMode":"sequential",
+      "actions":[
+        {
+          "functionRef": {
+            "refName": "sendRejectionEmailFunction",
+            "arguments": {
+              "applicant": "${ .applicant }"
+            }
+          }
+        }
+      ],
+      "end": true
+    }
+  ]
+}
\ No newline at end of file
diff --git a/api/src/test/resources/features/applicantrequest-with-key.yml b/api/src/test/resources/features/applicantrequest-with-key.yml
new file mode 100644
index 00000000..85beed74
--- /dev/null
+++ b/api/src/test/resources/features/applicantrequest-with-key.yml
@@ -0,0 +1,33 @@
+key: applicant-key-request
+version: '1.0'
+specVersion: '0.8'
+name: Applicant Request Decision Workflow
+description: Determine if applicant request is valid
+start: CheckApplication
+functions:
+  - name: sendRejectionEmailFunction
+    operation: http://myapis.org/applicationapi.json#emailRejection
+states:
+  - name: CheckApplication
+    type: switch
+    dataConditions:
+      - condition: "${ .applicants | .age >= 18 }"
+        transition: StartApplication
+      - condition: "${ .applicants | .age < 18 }"
+        transition: RejectApplication
+    defaultCondition:
+      transition: RejectApplication
+  - name: StartApplication
+    type: operation
+    actions:
+      - subFlowRef: startApplicationWorkflowId
+    end: true
+  - name: RejectApplication
+    type: operation
+    actionMode: sequential
+    actions:
+      - functionRef:
+          refName: sendRejectionEmailFunction
+          arguments:
+            applicant: "${ .applicant }"
+    end: true
diff --git a/api/src/test/resources/features/authbasic.json b/api/src/test/resources/features/authbasic.json
index 92397db4..31e86599 100644
--- a/api/src/test/resources/features/authbasic.json
+++ b/api/src/test/resources/features/authbasic.json
@@ -1,13 +1,15 @@
 {
-  "id" : "test-workflow",
-  "name" : "test-workflow-name",
-  "version" : "1.0",
-  "auth" : {
-    "name" : "authname",
-    "scheme" : "basic",
-    "properties" : {
-      "username" : "testuser",
-      "password" : "testpassword"
+  "id": "test-workflow",
+  "name": "test-workflow-name",
+  "version": "1.0",
+  "auth": [
+    {
+      "name": "authname",
+      "scheme": "basic",
+      "properties": {
+        "username": "testuser",
+        "password": "testpassword"
+      }
     }
-  }
+  ]
 }
\ No newline at end of file
diff --git a/api/src/test/resources/features/authbasic.yml b/api/src/test/resources/features/authbasic.yml
index 963dc63e..e04d1f7e 100644
--- a/api/src/test/resources/features/authbasic.yml
+++ b/api/src/test/resources/features/authbasic.yml
@@ -2,8 +2,8 @@ id: test-workflow
 name: test-workflow-name
 version: '1.0'
 auth:
-  name: authname
-  scheme: basic
-  properties:
-    username: testuser
-    password: testpassword
+  - name: authname
+    scheme: basic
+    properties:
+      username: testuser
+      password: testpassword
diff --git a/api/src/test/resources/features/authbearer.json b/api/src/test/resources/features/authbearer.json
index 304d1685..be7c037a 100644
--- a/api/src/test/resources/features/authbearer.json
+++ b/api/src/test/resources/features/authbearer.json
@@ -1,12 +1,14 @@
 {
-  "id" : "test-workflow",
-  "name" : "test-workflow-name",
-  "version" : "1.0",
-  "auth" : {
-    "name" : "authname",
-    "scheme" : "bearer",
-    "properties" : {
-      "token" : "testtoken"
+  "id": "test-workflow",
+  "name": "test-workflow-name",
+  "version": "1.0",
+  "auth": [
+    {
+      "name": "authname",
+      "scheme": "bearer",
+      "properties": {
+        "token": "testtoken"
+      }
     }
-  }
+  ]
 }
\ No newline at end of file
diff --git a/api/src/test/resources/features/authbearer.yml b/api/src/test/resources/features/authbearer.yml
index 0a815386..292fa3c2 100644
--- a/api/src/test/resources/features/authbearer.yml
+++ b/api/src/test/resources/features/authbearer.yml
@@ -2,7 +2,7 @@ id: test-workflow
 name: test-workflow-name
 version: '1.0'
 auth:
-  name: authname
-  scheme: bearer
-  properties:
-    token: testtoken
+  - name: authname
+    scheme: bearer
+    properties:
+      token: testtoken
diff --git a/api/src/test/resources/features/authoauth.json b/api/src/test/resources/features/authoauth.json
index da845606..10b76d70 100644
--- a/api/src/test/resources/features/authoauth.json
+++ b/api/src/test/resources/features/authoauth.json
@@ -2,7 +2,7 @@
   "id" : "test-workflow",
   "name" : "test-workflow-name",
   "version" : "1.0",
-  "auth" : {
+  "auth" : [{
     "name" : "authname",
     "scheme" : "oauth2",
     "properties" : {
@@ -11,5 +11,5 @@
       "clientId": "${ $SECRETS.clientid }",
       "clientSecret": "${ $SECRETS.clientsecret }"
     }
-  }
+  }]
 }
\ No newline at end of file
diff --git a/api/src/test/resources/features/authoauth.yml b/api/src/test/resources/features/authoauth.yml
index 8741297c..cb2c52ba 100644
--- a/api/src/test/resources/features/authoauth.yml
+++ b/api/src/test/resources/features/authoauth.yml
@@ -2,10 +2,10 @@ id: test-workflow
 name: test-workflow-name
 version: '1.0'
 auth:
-  name: authname
-  scheme: oauth2
-  properties:
-    authority: testauthority
-    grantType: clientCredentials
-    clientId: "${ $SECRETS.clientid }"
-    clientSecret: "${ $SECRETS.clientsecret }"
+  - name: authname
+    scheme: oauth2
+    properties:
+      authority: testauthority
+      grantType: clientCredentials
+      clientId: "${ $SECRETS.clientid }"
+      clientSecret: "${ $SECRETS.clientsecret }"
diff --git a/api/src/test/resources/features/eventdefdataonly.json b/api/src/test/resources/features/eventdefdataonly.json
new file mode 100644
index 00000000..a181b864
--- /dev/null
+++ b/api/src/test/resources/features/eventdefdataonly.json
@@ -0,0 +1,73 @@
+{
+  "id": "eventdefdataonly",
+  "version": "1.0",
+  "specVersion": "0.8",
+  "name": "Event Definition Data Only Test",
+  "description": "Event Definition Data Only Test",
+  "start": "CheckVisaStatus",
+  "events": [
+    {
+      "name": "visaApprovedEvent",
+      "type": "VisaApproved",
+      "source": "visaCheckSource",
+      "dataOnly": false
+    },
+    {
+      "name": "visaRejectedEvent",
+      "type": "VisaRejected",
+      "source": "visaCheckSource"
+    }
+  ],
+  "states":[
+    {
+      "name":"CheckVisaStatus",
+      "type":"switch",
+      "eventConditions": [
+        {
+          "eventRef": "visaApprovedEvent",
+          "transition": "HandleApprovedVisa"
+        },
+        {
+          "eventRef": "visaRejectedEvent",
+          "transition": "HandleRejectedVisa"
+        }
+      ],
+      "timeouts": {
+        "eventTimeout": "PT1H"
+      },
+      "defaultCondition": {
+        "transition": "HandleNoVisaDecision"
+      }
+    },
+    {
+      "name": "HandleApprovedVisa",
+      "type": "operation",
+      "actions": [
+        {
+          "subFlowRef": "handleApprovedVisaWorkflowID"
+        }
+      ],
+      "end": true
+    },
+    {
+      "name": "HandleRejectedVisa",
+      "type": "operation",
+      "actions": [
+        {
+          "subFlowRef": "handleRejectedVisaWorkflowID"
+        }
+      ],
+      "end": true
+    },
+    {
+      "name": "HandleNoVisaDecision",
+      "type": "operation",
+      "actions": [
+        {
+          "subFlowRef": "handleNoVisaDecisionWorkflowId"
+        }
+      ],
+      "end": true
+    }
+  ]
+}
\ No newline at end of file
diff --git a/api/src/test/resources/features/eventdefdataonly.yml b/api/src/test/resources/features/eventdefdataonly.yml
new file mode 100644
index 00000000..e67a9ede
--- /dev/null
+++ b/api/src/test/resources/features/eventdefdataonly.yml
@@ -0,0 +1,41 @@
+id: eventdefdataonly
+version: '1.0'
+specVersion: '0.8'
+name: Event Definition Data Only Test
+description: Event Definition Data Only Test
+start: CheckVisaStatus
+events:
+  - name: visaApprovedEvent
+    type: VisaApproved
+    source: visaCheckSource
+    dataOnly: false
+  - name: visaRejectedEvent
+    type: VisaRejected
+    source: visaCheckSource
+states:
+  - name: CheckVisaStatus
+    type: switch
+    eventConditions:
+      - eventRef: visaApprovedEvent
+        transition: HandleApprovedVisa
+      - eventRef: visaRejectedEvent
+        transition: HandleRejectedVisa
+    timeouts:
+      eventTimeout: PT1H
+    defaultCondition:
+      transition: HandleNoVisaDecision
+  - name: HandleApprovedVisa
+    type: operation
+    actions:
+      - subFlowRef: handleApprovedVisaWorkflowID
+    end: true
+  - name: HandleRejectedVisa
+    type: operation
+    actions:
+      - subFlowRef: handleRejectedVisaWorkflowID
+    end: true
+  - name: HandleNoVisaDecision
+    type: operation
+    actions:
+      - subFlowRef: handleNoVisaDecisionWorkflowId
+    end: true
diff --git a/api/src/test/resources/features/transitions.json b/api/src/test/resources/features/transitions.json
index cacc94af..ed7b7626 100644
--- a/api/src/test/resources/features/transitions.json
+++ b/api/src/test/resources/features/transitions.json
@@ -23,7 +23,11 @@
             "produceEvents": [
               {
                 "eventRef": "provisioningCompleteEvent",
-                "data": "${ .provisionedOrders }"
+                "data": "${ .provisionedOrders }",
+                "contextAttributes": {
+                  "order_location": "IN",
+                  "order_type": "online"
+                }
               }
             ]
           }
diff --git a/api/src/test/resources/features/transitions.yml b/api/src/test/resources/features/transitions.yml
index 1b89a85f..3ec34ae4 100644
--- a/api/src/test/resources/features/transitions.yml
+++ b/api/src/test/resources/features/transitions.yml
@@ -18,6 +18,9 @@ states:
           produceEvents:
             - eventRef: provisioningCompleteEvent
               data: "${ .provisionedOrders }"
+              contextAttributes:
+                "order_location": "IN"
+                "order_type": "online"
     defaultCondition:
       transition:
         nextState: RejectApplication
diff --git a/code-of-conduct.md b/code-of-conduct.md
index ddd14b6d..97a8526a 100644
--- a/code-of-conduct.md
+++ b/code-of-conduct.md
@@ -1,58 +1,11 @@
-## CNCF Community Code of Conduct v1.0
+# Code of Conduct
 
-Other languages available:
-- [Chinese/中文](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/zh.md)
-- [German/Deutsch](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/de.md)
-- [Spanish/Español](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/es.md)
-- [French/Français](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/fr.md)
-- [Italian/Italiano](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/it.md)
-- [Japanese/日本語](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/jp.md)
-- [Korean/한국어](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ko.md)
-- [Ukrainian/Українська](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/uk.md)
-- [Russian/Русский](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ru.md)
-- [Portuguese/Português](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/pt.md)
-- [Arabic/العربية](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ar.md)
-- [Polish/Polski](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/pl.md)
+We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
 
-### Contributor Code of Conduct
-
-As contributors and maintainers of this project, and in the interest of fostering
-an open and welcoming community, we pledge to respect all people who contribute
-through reporting issues, posting feature requests, updating documentation,
-submitting pull requests or patches, and other activities.
-
-We are committed to making participation in this project a harassment-free experience for
-everyone, regardless of level of experience, gender, gender identity and expression,
-sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
-religion, or nationality.
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery
-* Personal attacks
-* Trolling or insulting/derogatory comments
-* Public or private harassment
-* Publishing others' private information, such as physical or electronic addresses,
- without explicit permission
-* Other unethical or unprofessional conduct.
-
-Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are not
-aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers
-commit themselves to fairly and consistently applying these principles to every aspect
-of managing this project. Project maintainers who do not follow or enforce the Code of
-Conduct may be permanently removed from the project team.
-
-This code of conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior in Kubernetes may be reported by contacting the [Kubernetes Code of Conduct Committee](https://git.k8s.io/community/committee-code-of-conduct) via conduct@kubernetes.io. For other projects, please contact a CNCF project maintainer or our mediator, Mishi Choudhary via mishi@linux.com.
-
-This Code of Conduct is adapted from the Contributor Covenant
-(<http://contributor-covenant.org>), version 1.2.0, available at
-<http://contributor-covenant.org/version/1/2/0/>
-
-### CNCF Events Code of Conduct
-
-CNCF events are governed by the Linux Foundation [Code of Conduct](https://events.linuxfoundation.org/code-of-conduct/) available on the event page.
-This is designed to be compatible with the above policy and also includes more details on responding to incidents.
\ No newline at end of file
+<!-- TODO: Decide who will handle Code of Conduct reports and replace [INSERT EMAIL ADDRESS]
+    with an email address in the paragraph below. We recommend using a mailing list to handle reports.
+    If your project isn't prepared to handle reports, remove the project email address and just have
+    reporters send to conduct@cncf.io.
+-->
+Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io)
+in order to report violations of the Code of Conduct.
diff --git a/diagram/pom.xml b/diagram/pom.xml
index 41f8337a..d68a56cb 100644
--- a/diagram/pom.xml
+++ b/diagram/pom.xml
@@ -1,17 +1,14 @@
-<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">
+<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>io.serverlessworkflow</groupId>
         <artifactId>serverlessworkflow-parent</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
+        <version>4.3.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>serverlessworkflow-diagram</artifactId>
     <name>Serverless Workflow :: Diagram</name>
-    <version>${project.parent.version}</version>
     <packaging>jar</packaging>
     <description>Java SDK for Serverless Workflow Specification</description>
 
@@ -97,13 +94,13 @@
                     <checkstyleRules>
                         <module name="Checker">
                             <module name="RegexpHeader">
-                                <property name="header" value="${checkstyle.header.template}"/>
-                                <property name="fileExtensions" value="${checkstyle.header.extensions}"/>
+                                <property name="header" value="${checkstyle.header.template}" />
+                                <property name="fileExtensions" value="${checkstyle.header.extensions}" />
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="@author"/>
-                                    <property name="message" value="No @author tag allowed"/>
+                                    <property name="format" value="@author" />
+                                    <property name="message" value="No @author tag allowed" />
                                 </module>
                             </module>
                         </module>
@@ -130,26 +127,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>com.coveo</groupId>
-                <artifactId>fmt-maven-plugin</artifactId>
-                <configuration>
-                    <sourceDirectory>src/main/java</sourceDirectory>
-                    <testSourceDirectory>src/test/java</testSourceDirectory>
-                    <verbose>false</verbose>
-                    <filesNamePattern>.*\.java</filesNamePattern>
-                    <skip>false</skip>
-                    <skipSortingImports>false</skipSortingImports>
-                    <style>google</style>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>format</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 </project>
\ No newline at end of file
diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java b/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java
index 5a2d4028..1fb5e656 100644
--- a/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java
+++ b/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java
@@ -26,9 +26,14 @@
 
 public class WorkflowDiagramImpl implements WorkflowDiagram {
 
+  public static final String DEFAULT_TEMPLATE = "workflow-template";
+
   @SuppressWarnings("unused")
   private String source;
 
+  @SuppressWarnings("unused")
+  private String template = DEFAULT_TEMPLATE;
+
   private Workflow workflow;
   private boolean showLegend = false;
 
@@ -46,12 +51,18 @@ public WorkflowDiagram setSource(String source) {
     return this;
   }
 
+  @Override
+  public WorkflowDiagram setTemplate(String template) {
+    this.template = template;
+    return this;
+  }
+
   @Override
   public String getSvgDiagram() throws Exception {
     if (workflow == null) {
       throw new IllegalAccessException("Unable to get diagram - no workflow set.");
     }
-    String diagramSource = WorkflowToPlantuml.convert(workflow, showLegend);
+    String diagramSource = WorkflowToPlantuml.convert(template, workflow, showLegend);
     SourceStringReader reader = new SourceStringReader(diagramSource);
     final ByteArrayOutputStream os = new ByteArrayOutputStream();
     reader.generateImage(os, new FileFormatOption(FileFormat.SVG));
diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java
index acc112b8..956bcbeb 100644
--- a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java
+++ b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java
@@ -22,11 +22,12 @@
 import org.thymeleaf.context.Context;
 
 public class WorkflowToPlantuml {
-  public static String convert(Workflow workflow, boolean showLegend) {
+
+  public static String convert(String template, Workflow workflow, boolean showLegend) {
     TemplateEngine plantUmlTemplateEngine = ThymeleafConfig.templateEngine;
     Context context = new Context();
     context.setVariable("diagram", new WorkflowDiagramModel(workflow, showLegend));
 
-    return plantUmlTemplateEngine.process("workflow-template", context);
+    return plantUmlTemplateEngine.process(template, context);
   }
 }
diff --git a/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java b/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java
new file mode 100644
index 00000000..5c9b7038
--- /dev/null
+++ b/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed 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 io.serverlessworkflow.diagram.test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.serverlessworkflow.api.Workflow;
+import io.serverlessworkflow.api.interfaces.WorkflowDiagram;
+import io.serverlessworkflow.diagram.WorkflowDiagramImpl;
+import io.serverlessworkflow.diagram.test.utils.DiagramTestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class CustomTemplateWorkflowDiagramTest {
+
+  @ParameterizedTest
+  @ValueSource(strings = {"/examples/applicantrequest.json", "/examples/applicantrequest.yml"})
+  public void testSpecExamplesParsing(String workflowLocation) throws Exception {
+
+    Workflow workflow = Workflow.fromSource(DiagramTestUtils.readWorkflowFile(workflowLocation));
+
+    assertNotNull(workflow);
+    assertNotNull(workflow.getId());
+    assertNotNull(workflow.getName());
+    assertNotNull(workflow.getStates());
+
+    WorkflowDiagram workflowDiagram =
+        new WorkflowDiagramImpl()
+            .showLegend(true)
+            .setWorkflow(workflow)
+            .setTemplate("custom-template");
+
+    String diagramSVG = workflowDiagram.getSvgDiagram();
+
+    Assertions.assertNotNull(diagramSVG);
+    // custom template uses customcolor in the legend
+    Assertions.assertTrue(diagramSVG.contains("customcolor"));
+  }
+}
diff --git a/diagram/src/test/resources/templates/plantuml/custom-template.txt b/diagram/src/test/resources/templates/plantuml/custom-template.txt
new file mode 100644
index 00000000..e162afc5
--- /dev/null
+++ b/diagram/src/test/resources/templates/plantuml/custom-template.txt
@@ -0,0 +1,46 @@
+@startuml
+skinparam backgroundColor White
+skinparam legendBackgroundColor White
+skinparam legendBorderColor White
+skinparam state {
+  StartColor Green
+  EndColor Orange
+  BackgroundColor GhostWhite
+  BackgroundColor<< workflow >> White
+  BorderColor Black
+  ArrowColor Black
+
+  BorderColor<< event >> #7fe5f0
+  BorderColor<< operation >> #bada55
+  BorderColor<< switch >> #92a0f2
+  BorderColor<< sleep >> #b83b5e
+  BorderColor<< parallel >> #6a2c70
+  BorderColor<< inject >> #1e5f74
+  BorderColor<< foreach >> #931a25
+  BorderColor<< callback >> #ffcb8e
+}
+state "[(${diagram.title})]" as workflow << workflow >> {
+
+[# th:each="stateDef : ${diagram.modelStateDefs}" ]
+[(${stateDef.toString()})]
+[/]
+
+[# th:each="state : ${diagram.modelStates}" ]
+[(${state.toString()})]
+[/]
+
+[# th:each="connection : ${diagram.modelConnections}" ]
+[(${connection.toString()})]
+[/]
+
+}
+
+[# th:if="${diagram.showLegend}" ]
+legend center
+State Types and Border Colors:
+| Event | Operation | Switch | Sleep | Parallel | Inject | ForEach | CallBack |
+|<#7fe5f0>|<#bada55>|<#92a0f2>|<#b83b5e>|<#6a2c70>|<#1e5f74>|<#931a25>|<customcolor>|
+endlegend
+[/]
+
+@enduml
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5ef1c788..f87d5930 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,74 +1,96 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<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/maven-v4_0_0.xsd">
-    <modelVersion>4.0.0</modelVersion>
+<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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
 
-    <groupId>io.serverlessworkflow</groupId>
-    <artifactId>serverlessworkflow-parent</artifactId>
-    <version>4.0.0-SNAPSHOT</version>
-    <packaging>pom</packaging>
+  <groupId>io.serverlessworkflow</groupId>
+  <artifactId>serverlessworkflow-parent</artifactId>
+  <version>4.3.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
 
-    <name>Serverless Workflow :: Parent</name>
-    <url>https://serverlessworkflow.io/sdk-java/</url>
-    <description>Java SDK for Serverless Workflow Specification</description>
-    <inceptionYear>2020</inceptionYear>
-    <organization>
-        <name>CNCF</name>
-        <url>https://www.cncf.io//</url>
-    </organization>
-    <licenses>
-        <license>
-            <name>The Apache Software License, Version 2.0</name>
-            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
-            <distribution>repo</distribution>
-        </license>
-    </licenses>
+  <name>Serverless Workflow :: Parent</name>
+  <url>https://serverlessworkflow.io/sdk-java/</url>
+  <description>Java SDK for Serverless Workflow Specification</description>
+  <inceptionYear>2020</inceptionYear>
+  <developers>
+    <developer>
+      <id>serverless-workflow</id>
+      <name>Serverless Workflow Specification Authors</name>
+      <organization>CNCF</organization>
+    </developer>
+  </developers>
+  <organization>
+    <name>CNCF</name>
+    <url>https://www.cncf.io//</url>
+  </organization>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <connection>scm:git:git@github.com:serverlessworkflow/sdk-java.git</connection>
+    <developerConnection>scm:git:git@github.com:serverlessworkflow/sdk-java.git</developerConnection>
+    <url>https://github.com/serverlessworkflow/sdk-java</url>
+    <tag>HEAD</tag>
+  </scm>
 
-    <modules>
-        <module>api</module>
-        <module>spi</module>
-        <module>validation</module>
-        <module>diagram</module>
-        <module>utils</module>
-    </modules>
+  <modules>
+    <module>api</module>
+    <module>spi</module>
+    <module>validation</module>
+    <module>diagram</module>
+    <module>utils</module>
+  </modules>
 
-    <properties>
-        <maven.compiler.source>1.8</maven.compiler.source>
-        <maven.compiler.target>1.8</maven.compiler.target>
-        <java.version>1.8</java.version>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <version.maven>3.6.2</version.maven>
-        <version.enforcer.plugin>3.0.0-M2</version.enforcer.plugin>
-        <version.jdk>8</version.jdk>
-        <version.compiler.plugin>3.8.1</version.compiler.plugin>
-        <version.surefire.plugin>2.22.0</version.surefire.plugin>
-        <version.failsafe.plugin>2.22.0</version.failsafe.plugin>
-        <version.checkstyle.plugin>3.1.1</version.checkstyle.plugin>
-        <version.deploy.plugin>2.8.2</version.deploy.plugin>
+  <properties>
+    <java.version>17</java.version>
+    <maven.compiler.source>${java.version}</maven.compiler.source>
+    <maven.compiler.target>${java.version}</maven.compiler.target>
+    <maven.compiler.release>${java.version}</maven.compiler.release>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <version.maven>3.9.7</version.maven>
 
-        <version.org.slf4j>1.7.25</version.org.slf4j>
-        <version.com.fasterxml.jackson>2.10.3</version.com.fasterxml.jackson>
-        <version.javax.validation>2.0.1.Final</version.javax.validation>
-        <version.org.junit.minor>6.0</version.org.junit.minor>
-        <version.org.junit>5.${version.org.junit.minor}</version.org.junit>
-        <version.org.junit.jupiter>${version.org.junit}</version.org.junit.jupiter>
-        <version.org.mockito>3.0.0</version.org.mockito>
-        <version.ch.qos.logback>1.1.3</version.ch.qos.logback>
-        <version.org.assertj>3.13.2</version.org.assertj>
-        <version.jsonschema2pojo-maven-plugin>1.0.1</version.jsonschema2pojo-maven-plugin>
-        <commons.lang.version>3.9</commons.lang.version>
-        <hamcrest.version>1.3</hamcrest.version>
-        <jsonassert.version>1.5.0</jsonassert.version>
-        <json.schema.validation.version>1.12.1</json.schema.validation.version>
-        <version.json>20200518</version.json>
-        <version.thymeleaf>3.0.11.RELEASE</version.thymeleaf>
-        <version.plantuml>8059</version.plantuml>
-        <version.graphviz>0.17.0</version.graphviz>
-        <version.fmt-maven-plugin>2.9</version.fmt-maven-plugin>
+    <!-- Plugin versions, please keep in alphabetical order -->
+    <version.buildnumber.plugin>3.2.1</version.buildnumber.plugin>
+    <version.checkstyle.plugin>3.6.0</version.checkstyle.plugin>
+    <version.compiler.plugin>3.14.0</version.compiler.plugin>
+    <version.deploy.plugin>3.1.4</version.deploy.plugin>
+    <version.enforcer.plugin>3.6.1</version.enforcer.plugin>
+    <version.failsafe.plugin>3.5.3</version.failsafe.plugin>
+    <version.fmt-maven-plugin>2.25</version.fmt-maven-plugin>
+    <version.gpg.plugin>3.2.8</version.gpg.plugin>
+    <version.jar.plugin>3.4.2</version.jar.plugin>
+    <version.jdk>${java.version}</version.jdk>
+    <version.jsonschema2pojo-maven-plugin>1.2.2</version.jsonschema2pojo-maven-plugin>
+    <version.javadoc.plugin>3.11.2</version.javadoc.plugin>
+    <version.release.plugin>3.1.1</version.release.plugin>
+    <version.source.plugin>3.3.1</version.source.plugin>
+    <version.surefire.plugin>3.5.3</version.surefire.plugin>
+    <version.nexus.plugin>1.7.0</version.nexus.plugin>
 
-        <!-- Checkstyle props -->
-        <checkstyle.failOnViolation>true</checkstyle.failOnViolation>
-        <checkstyle.header.template><![CDATA[
+
+    <!-- Dependencies versions, please keep in alphabetical order -->
+    <version.ch.qos.logback>1.5.18</version.ch.qos.logback>
+    <version.com.fasterxml.jackson>2.19.2</version.com.fasterxml.jackson>
+    <version.com.networknt>1.5.8</version.com.networknt>
+    <version.commons.lang>3.18.0</version.commons.lang>
+    <version.graphviz>0.18.1</version.graphviz>
+    <version.hamcrest>3.0</version.hamcrest>
+    <version.jakarta.validation>3.1.1</version.jakarta.validation>
+    <version.jsonassert>1.5.3</version.jsonassert>
+    <version.org.assertj>3.27.4</version.org.assertj>
+    <version.org.junit.jupiter>5.13.4</version.org.junit.jupiter>
+    <version.org.mockito>5.18.0</version.org.mockito>
+    <version.org.slf4j>2.0.17</version.org.slf4j>
+    <version.plantuml>8059</version.plantuml>
+    <version.thymeleaf>3.1.3.RELEASE</version.thymeleaf>
+
+
+    <!-- Checkstyle props -->
+    <checkstyle.failOnViolation>true</checkstyle.failOnViolation>
+    <checkstyle.header.template><![CDATA[
 ^\/\*$\n^
 \* Copyright \d\d\d\d-Present The Serverless Workflow Specification Authors$\n^
 \*$\n^
@@ -85,231 +107,425 @@
 \* limitations under the License\.$\n^
 \*\/$
 ]]>
-        </checkstyle.header.template>
-        <checkstyle.header.extensions>java</checkstyle.header.extensions>
-        <checkstyle.logViolationsToConsole>true</checkstyle.logViolationsToConsole>
-    </properties>
+    </checkstyle.header.template>
+    <checkstyle.header.extensions>java</checkstyle.header.extensions>
+    <checkstyle.logViolationsToConsole>true</checkstyle.logViolationsToConsole>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>${version.org.slf4j}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>jcl-over-slf4j</artifactId>
+        <version>${version.org.slf4j}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-core</artifactId>
+        <version>${version.com.fasterxml.jackson}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-databind</artifactId>
+        <version>${version.com.fasterxml.jackson}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.networknt</groupId>
+        <artifactId>json-schema-validator</artifactId>
+        <version>${version.com.networknt}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.dataformat</groupId>
+        <artifactId>jackson-dataformat-yaml</artifactId>
+        <version>${version.com.fasterxml.jackson}</version>
+      </dependency>
+      <dependency>
+        <groupId>jakarta.validation</groupId>
+        <artifactId>jakarta.validation-api</artifactId>
+        <version>${version.jakarta.validation}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-lang3</artifactId>
+        <version>${version.commons.lang}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.thymeleaf</groupId>
+        <artifactId>thymeleaf</artifactId>
+        <version>${version.thymeleaf}</version>
+      </dependency>
+      <dependency>
+        <groupId>net.sourceforge.plantuml</groupId>
+        <artifactId>plantuml</artifactId>
+        <version>${version.plantuml}</version>
+      </dependency>
+      <dependency>
+        <groupId>guru.nidi</groupId>
+        <artifactId>graphviz-java</artifactId>
+        <version>${version.graphviz}</version>
+      </dependency>
+
+      <!-- test -->
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-api</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-engine</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-params</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-core</artifactId>
+        <version>${version.org.mockito}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>ch.qos.logback</groupId>
+        <artifactId>logback-classic</artifactId>
+        <version>${version.ch.qos.logback}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.assertj</groupId>
+        <artifactId>assertj-core</artifactId>
+        <version>${version.org.assertj}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-library</artifactId>
+        <version>${version.hamcrest}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.skyscreamer</groupId>
+        <artifactId>jsonassert</artifactId>
+        <version>${version.jsonassert}</version>
+        <scope>test</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-nop</artifactId>
+      <version>${version.org.slf4j}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <!-- Set properties containing the scm revision -->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>buildnumber-maven-plugin</artifactId>
+        <version>${version.buildnumber.plugin}</version>
+        <executions>
+          <execution>
+            <id>get-scm-revision</id>
+            <phase>initialize</phase>
+            <goals>
+              <goal>create</goal>
+            </goals>
+            <configuration>
+              <doCheck>false</doCheck>
+              <doUpdate>false</doUpdate>
+              <revisionOnScmFailure>UNKNOWN</revisionOnScmFailure>
+              <getRevisionOnlyOnce>true</getRevisionOnlyOnce>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>${version.compiler.plugin}</version>
+        <configuration>
+          <showDeprecation>true</showDeprecation>
+          <showWarnings>true</showWarnings>
+          <source>${maven.compiler.source}</source>
+          <target>${maven.compiler.target}</target>
+          <testSource>${maven.compiler.source}</testSource>
+          <testTarget>${maven.compiler.target}</testTarget>
+          <parameters>true</parameters>
+          <compilerArgs>
+            <arg>-Xlint:unchecked</arg>
+          </compilerArgs>
+        </configuration>
+      </plugin>
+    </plugins>
+
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.sonatype.plugins</groupId>
+          <artifactId>nexus-staging-maven-plugin</artifactId>
+          <version>${version.nexus.plugin}</version>
+          <extensions>true</extensions>
+          <configuration>
+            <serverId>ossrh</serverId>
+            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+            <autoReleaseAfterClose>true</autoReleaseAfterClose>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-gpg-plugin</artifactId>
+          <version>${version.gpg.plugin}</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>${version.deploy.plugin}</version>
+          <configuration>
+            <retryFailedDeploymentCount>10</retryFailedDeploymentCount>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-enforcer-plugin</artifactId>
+          <version>${version.enforcer.plugin}</version>
+          <executions>
+            <execution>
+              <id>enforce-versions</id>
+              <goals>
+                <goal>enforce</goal>
+              </goals>
+              <configuration>
+                <rules>
+                  <requireMavenVersion>
+                    <version>${version.maven}</version>
+                  </requireMavenVersion>
+                  <requireJavaVersion>
+                    <version>${version.jdk}</version>
+                  </requireJavaVersion>
+                </rules>
+              </configuration>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-source-plugin</artifactId>
+          <version>${version.source.plugin}</version>
+          <executions>
+            <execution>
+              <id>attach-sources</id>
+              <goals>
+                <goal>jar-no-fork</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <archive>
+              <index>true</index>
+              <manifest>
+                <addDefaultSpecificationEntries>
+                  true
+                </addDefaultSpecificationEntries>
+                <addDefaultImplementationEntries>
+                  true
+                </addDefaultImplementationEntries>
+              </manifest>
+              <manifestEntries>
+                <Implementation-URL>${project.url}</Implementation-URL>
+                <Java-Version>${java.version}</Java-Version>
+                <Java-Vendor>${java.vendor}</Java-Vendor>
+                <Os-Name>${os.name}</Os-Name>
+                <Os-Arch>${os.arch}</Os-Arch>
+                <Os-Version>${os.version}</Os-Version>
+                <Scm-Url>${project.scm.url}</Scm-Url>
+                <Scm-Connection>${project.scm.connection}</Scm-Connection>
+                <Scm-Revision>${buildNumber}</Scm-Revision>
+              </manifestEntries>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-release-plugin</artifactId>
+          <version>${version.release.plugin}</version>
+          <configuration>
+            <preparationGoals>clean install</preparationGoals>
+            <autoVersionSubmodules>true</autoVersionSubmodules>
+            <tagNameFormat>@{project.version}</tagNameFormat>
+            <pushChanges>false</pushChanges>
+            <localCheckout>true</localCheckout>
+            <remoteTagging>false</remoteTagging>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.jsonschema2pojo</groupId>
+          <artifactId>jsonschema2pojo-maven-plugin</artifactId>
+          <version>${version.jsonschema2pojo-maven-plugin}</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>${version.surefire.plugin}</version>
+          <configuration>
+            <argLine>-Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m</argLine>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-failsafe-plugin</artifactId>
+          <version>${version.failsafe.plugin}</version>
+          <configuration>
+            <argLine>-Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m</argLine>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-checkstyle-plugin</artifactId>
+          <version>${version.checkstyle.plugin}</version>
+        </plugin>
+        <plugin>
+          <groupId>com.spotify.fmt</groupId>
+          <artifactId>fmt-maven-plugin</artifactId>
+          <configuration>
+            <sourceDirectory>src/main/java</sourceDirectory>
+            <testSourceDirectory>src/test/java</testSourceDirectory>
+            <verbose>false</verbose>
+            <filesNamePattern>.*\.java</filesNamePattern>
+            <skip>false</skip>
+            <skipSortingImports>false</skipSortingImports>
+            <style>google</style>
+          </configuration>
+          <executions>
+            <execution>
+              <goals>
+                <goal>format</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>${version.jar.plugin}</version>
+          <configuration>
+            <archive>
+              <index>true</index>
+              <manifest>
+                <addDefaultSpecificationEntries>
+                  true
+                </addDefaultSpecificationEntries>
+                <addDefaultImplementationEntries>
+                  true
+                </addDefaultImplementationEntries>
+              </manifest>
+              <manifestEntries>
+                <Implementation-URL>${project.url}</Implementation-URL>
+                <Java-Version>${java.version}</Java-Version>
+                <Java-Vendor>${java.vendor}</Java-Vendor>
+                <Os-Name>${os.name}</Os-Name>
+                <Os-Arch>${os.arch}</Os-Arch>
+                <Os-Version>${os.version}</Os-Version>
+                <Scm-Url>${project.scm.url}</Scm-Url>
+                <Scm-Connection>${project.scm.connection}</Scm-Connection>
+                <Scm-Revision>${buildNumber}</Scm-Revision>
+              </manifestEntries>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>${version.javadoc.plugin}</version>
+          <configuration>
+            <failOnError>false</failOnError>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
 
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>org.slf4j</groupId>
-                <artifactId>slf4j-api</artifactId>
-                <version>${version.org.slf4j}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.slf4j</groupId>
-                <artifactId>jcl-over-slf4j</artifactId>
-                <version>${version.org.slf4j}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.fasterxml.jackson.core</groupId>
-                <artifactId>jackson-core</artifactId>
-                <version>${version.com.fasterxml.jackson}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.fasterxml.jackson.core</groupId>
-                <artifactId>jackson-databind</artifactId>
-                <version>${version.com.fasterxml.jackson}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.fasterxml.jackson.dataformat</groupId>
-                <artifactId>jackson-dataformat-yaml</artifactId>
-                <version>${version.com.fasterxml.jackson}</version>
-            </dependency>
-            <dependency>
-                <groupId>javax.validation</groupId>
-                <artifactId>validation-api</artifactId>
-                <version>${version.javax.validation}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.commons</groupId>
-                <artifactId>commons-lang3</artifactId>
-                <version>${commons.lang.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.github.erosb</groupId>
-                <artifactId>everit-json-schema</artifactId>
-                <version>${json.schema.validation.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <groupId>commons-logging</groupId>
-                        <artifactId>commons-logging</artifactId>
-                    </exclusion>
-                </exclusions>
-            </dependency>
-            <dependency>
-                <groupId>org.json</groupId>
-                <artifactId>json</artifactId>
-                <version>${version.json}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.thymeleaf</groupId>
-                <artifactId>thymeleaf</artifactId>
-                <version>${version.thymeleaf}</version>
-            </dependency>
-            <dependency>
-                <groupId>net.sourceforge.plantuml</groupId>
-                <artifactId>plantuml</artifactId>
-                <version>${version.plantuml}</version>
-            </dependency>
-            <dependency>
-                <groupId>guru.nidi</groupId>
-                <artifactId>graphviz-java</artifactId>
-                <version>${version.graphviz}</version>
-            </dependency>
 
-            <!-- test -->
-            <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-api</artifactId>
-                <version>${version.org.junit.jupiter}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-engine</artifactId>
-                <version>${version.org.junit.jupiter}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-params</artifactId>
-                <version>${version.org.junit.jupiter}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.mockito</groupId>
-                <artifactId>mockito-core</artifactId>
-                <version>${version.org.mockito}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>ch.qos.logback</groupId>
-                <artifactId>logback-classic</artifactId>
-                <version>${version.ch.qos.logback}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.assertj</groupId>
-                <artifactId>assertj-core</artifactId>
-                <version>${version.org.assertj}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.hamcrest</groupId>
-                <artifactId>hamcrest-library</artifactId>
-                <version>${hamcrest.version}</version>
-                <scope>test</scope>
-            </dependency>
-            <dependency>
-                <groupId>org.skyscreamer</groupId>
-                <artifactId>jsonassert</artifactId>
-                <version>${jsonassert.version}</version>
-                <scope>test</scope>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
+  <distributionManagement>
+    <snapshotRepository>
+      <id>ossrh-snapshots</id>
+      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+    </snapshotRepository>
+    <repository>
+      <id>ossrh</id>
+      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+    </repository>
+  </distributionManagement>
 
-    <build>
-        <pluginManagement>
-            <plugins>
-                <plugin>
-                    <artifactId>maven-deploy-plugin</artifactId>
-                    <version>${version.deploy.plugin}</version>
-                    <configuration>
-                        <retryFailedDeploymentCount>10</retryFailedDeploymentCount>
-                    </configuration>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-enforcer-plugin</artifactId>
-                    <version>${version.enforcer.plugin}</version>
-                    <executions>
-                        <execution>
-                            <id>enforce-versions</id>
-                            <goals>
-                                <goal>enforce</goal>
-                            </goals>
-                            <configuration>
-                                <rules>
-                                    <requireMavenVersion>
-                                        <version>${version.maven}</version>
-                                    </requireMavenVersion>
-                                    <requireJavaVersion>
-                                        <version>${version.jdk}</version>
-                                    </requireJavaVersion>
-                                </rules>
-                            </configuration>
-                        </execution>
-                    </executions>
-                </plugin>
-                <plugin>
-                    <artifactId>maven-compiler-plugin</artifactId>
-                    <version>${version.compiler.plugin}</version>
-                    <configuration>
-                        <showDeprecation>true</showDeprecation>
-                        <showWarnings>true</showWarnings>
-                        <compilerArgs>
-                            <arg>-Xlint:unchecked</arg>
-                        </compilerArgs>
-                    </configuration>
-                </plugin>
-                <plugin>
-                    <groupId>org.jsonschema2pojo</groupId>
-                    <artifactId>jsonschema2pojo-maven-plugin</artifactId>
-                    <version>${version.jsonschema2pojo-maven-plugin}</version>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-surefire-plugin</artifactId>
-                    <version>${version.surefire.plugin}</version>
-                    <configuration>
-                       <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
-                    </configuration>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-failsafe-plugin</artifactId>
-                    <version>${version.failsafe.plugin}</version>
-                    <configuration>
-                       <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
-                    </configuration>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-checkstyle-plugin</artifactId>
-                    <version>${version.checkstyle.plugin}</version>
-                </plugin>
-                <plugin>
-                    <groupId>com.coveo</groupId>
-                    <artifactId>fmt-maven-plugin</artifactId>
-                    <version>${version.fmt-maven-plugin}</version>
-                </plugin>
-            </plugins>
-        </pluginManagement>
-    </build>
+  <pluginRepositories>
+    <pluginRepository>
+      <id>central</id>
+      <name>Central Repository</name>
+      <url>https://repo.maven.apache.org/maven2</url>
+      <layout>default</layout>
+      <snapshots>
+        <enabled>false</enabled>
+      </snapshots>
+    </pluginRepository>
+  </pluginRepositories>
 
-    <distributionManagement>
-        <snapshotRepository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
-        </snapshotRepository>
-        <repository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
-        </repository>
-    </distributionManagement>
+  <profiles>
+    <profile>
+      <id>release</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-gpg-plugin</artifactId>
+            <configuration>
+              <gpgArguments>
+                <arg>--pinentry-mode</arg>
+                <arg>loopback</arg>
+              </gpgArguments>
+            </configuration>
+            <executions>
+              <execution>
+                <id>sign-artifacts</id>
+                <phase>verify</phase>
+                <goals>
+                  <goal>sign</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <executions>
+              <execution>
+                <phase>package</phase>
+                <goals>
+                  <goal>jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
 
-    <pluginRepositories>
-        <pluginRepository>
-            <id>central</id>
-            <name>Central Repository</name>
-            <url>https://repo.maven.apache.org/maven2</url>
-            <layout>default</layout>
-            <snapshots>
-                <enabled>false</enabled>
-            </snapshots>
-        </pluginRepository>
-    </pluginRepositories>
-</project>
+</project>
\ No newline at end of file
diff --git a/spi/pom.xml b/spi/pom.xml
index e713102e..1bb2c028 100644
--- a/spi/pom.xml
+++ b/spi/pom.xml
@@ -1,17 +1,14 @@
-<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">
+<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>io.serverlessworkflow</groupId>
         <artifactId>serverlessworkflow-parent</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
+        <version>4.3.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>serverlessworkflow-spi</artifactId>
     <name>Serverless Workflow :: SPI</name>
-    <version>${project.parent.version}</version>
     <packaging>jar</packaging>
     <description>Java SDK for Serverless Workflow Specification</description>
 
@@ -69,13 +66,13 @@
                     <checkstyleRules>
                         <module name="Checker">
                             <module name="RegexpHeader">
-                                <property name="header" value="${checkstyle.header.template}"/>
-                                <property name="fileExtensions" value="${checkstyle.header.extensions}"/>
+                                <property name="header" value="${checkstyle.header.template}" />
+                                <property name="fileExtensions" value="${checkstyle.header.extensions}" />
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="@author"/>
-                                    <property name="message" value="No @author tag allowed"/>
+                                    <property name="format" value="@author" />
+                                    <property name="message" value="No @author tag allowed" />
                                 </module>
                             </module>
                         </module>
@@ -102,26 +99,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>com.coveo</groupId>
-                <artifactId>fmt-maven-plugin</artifactId>
-                <configuration>
-                    <sourceDirectory>src/main/java</sourceDirectory>
-                    <testSourceDirectory>src/test/java</testSourceDirectory>
-                    <verbose>false</verbose>
-                    <filesNamePattern>.*\.java</filesNamePattern>
-                    <skip>false</skip>
-                    <skipSortingImports>false</skipSortingImports>
-                    <style>google</style>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>format</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 </project>
\ No newline at end of file
diff --git a/utils/pom.xml b/utils/pom.xml
index be6a7444..23a23711 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -1,17 +1,14 @@
-<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">
+<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>io.serverlessworkflow</groupId>
         <artifactId>serverlessworkflow-parent</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
+        <version>4.3.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>serverlessworkflow-util</artifactId>
     <name>Serverless Workflow :: Utils</name>
-    <version>${project.parent.version}</version>
     <packaging>jar</packaging>
     <description>Java SDK for Serverless Workflow Specification</description>
 
@@ -69,13 +66,13 @@
                     <checkstyleRules>
                         <module name="Checker">
                             <module name="RegexpHeader">
-                                <property name="header" value="${checkstyle.header.template}"/>
-                                <property name="fileExtensions" value="${checkstyle.header.extensions}"/>
+                                <property name="header" value="${checkstyle.header.template}" />
+                                <property name="fileExtensions" value="${checkstyle.header.extensions}" />
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="@author"/>
-                                    <property name="message" value="No @author tag allowed"/>
+                                    <property name="format" value="@author" />
+                                    <property name="message" value="No @author tag allowed" />
                                 </module>
                             </module>
                         </module>
@@ -102,26 +99,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>com.coveo</groupId>
-                <artifactId>fmt-maven-plugin</artifactId>
-                <configuration>
-                    <sourceDirectory>src/main/java</sourceDirectory>
-                    <testSourceDirectory>src/test/java</testSourceDirectory>
-                    <verbose>false</verbose>
-                    <filesNamePattern>.*\.java</filesNamePattern>
-                    <skip>false</skip>
-                    <skipSortingImports>false</skipSortingImports>
-                    <style>google</style>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>format</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 </project>
\ No newline at end of file
diff --git a/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java b/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java
index 29b8c7cc..a5673b40 100644
--- a/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java
+++ b/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java
@@ -574,7 +574,7 @@ public static JsonNode mergeNodes(JsonNode mainNode, JsonNode updateNode) {
         if (mainNode instanceof ObjectNode) {
           // Overwrite field
           JsonNode value = updateNode.get(fieldName);
-          ((ObjectNode) mainNode).put(fieldName, value);
+          ((ObjectNode) mainNode).set(fieldName, value);
         }
       }
     }
@@ -591,7 +591,7 @@ public static JsonNode mergeNodes(JsonNode mainNode, JsonNode updateNode) {
    * @return original, main node with field added
    */
   public static JsonNode addNode(JsonNode mainNode, JsonNode toAddNode, String fieldName) {
-    ((ObjectNode) mainNode).put(fieldName, toAddNode);
+    ((ObjectNode) mainNode).set(fieldName, toAddNode);
     return mainNode;
   }
 
@@ -604,7 +604,7 @@ public static JsonNode addNode(JsonNode mainNode, JsonNode toAddNode, String fie
    * @return original, main node with array added
    */
   public static JsonNode addArray(JsonNode mainNode, ArrayNode toAddArray, String arrayName) {
-    ((ObjectNode) mainNode).put(arrayName, toAddArray);
+    ((ObjectNode) mainNode).set(arrayName, toAddArray);
     return mainNode;
   }
 
@@ -618,7 +618,7 @@ public static JsonNode addArray(JsonNode mainNode, ArrayNode toAddArray, String
    */
   public static JsonNode addFieldValue(JsonNode mainNode, Object toAddValue, String fieldName) {
     ObjectMapper mapper = new ObjectMapper();
-    ((ObjectNode) mainNode).put(fieldName, mapper.valueToTree(toAddValue));
+    ((ObjectNode) mainNode).set(fieldName, mapper.valueToTree(toAddValue));
     return mainNode;
   }
 }
diff --git a/validation/pom.xml b/validation/pom.xml
index 7219d952..63c7901f 100644
--- a/validation/pom.xml
+++ b/validation/pom.xml
@@ -1,17 +1,14 @@
-<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">
+<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>io.serverlessworkflow</groupId>
         <artifactId>serverlessworkflow-parent</artifactId>
-        <version>4.0.0-SNAPSHOT</version>
+        <version>4.3.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>serverlessworkflow-validation</artifactId>
     <name>Serverless Workflow :: Validation</name>
-    <version>${project.parent.version}</version>
     <packaging>jar</packaging>
     <description>Java SDK for Serverless Workflow Specification</description>
 
@@ -35,12 +32,10 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>com.github.erosb</groupId>
-            <artifactId>everit-json-schema</artifactId>
+          <groupId>com.networknt</groupId>
+          <artifactId>json-schema-validator</artifactId>
         </dependency>
-
         <!-- test -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -93,13 +88,13 @@
                     <checkstyleRules>
                         <module name="Checker">
                             <module name="RegexpHeader">
-                                <property name="header" value="${checkstyle.header.template}"/>
-                                <property name="fileExtensions" value="${checkstyle.header.extensions}"/>
+                                <property name="header" value="${checkstyle.header.template}" />
+                                <property name="fileExtensions" value="${checkstyle.header.extensions}" />
                             </module>
                             <module name="TreeWalker">
                                 <module name="RegexpSinglelineJava">
-                                    <property name="format" value="@author"/>
-                                    <property name="message" value="No @author tag allowed"/>
+                                    <property name="format" value="@author" />
+                                    <property name="message" value="No @author tag allowed" />
                                 </module>
                             </module>
                         </module>
@@ -126,26 +121,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>com.coveo</groupId>
-                <artifactId>fmt-maven-plugin</artifactId>
-                <configuration>
-                    <sourceDirectory>src/main/java</sourceDirectory>
-                    <testSourceDirectory>src/test/java</testSourceDirectory>
-                    <verbose>false</verbose>
-                    <filesNamePattern>.*\.java</filesNamePattern>
-                    <skip>false</skip>
-                    <skipSortingImports>false</skipSortingImports>
-                    <style>google</style>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>format</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java
index 130e4c24..12e4e915 100644
--- a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java
+++ b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java
@@ -15,8 +15,9 @@
  */
 package io.serverlessworkflow.validation;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion.VersionFlag;
 import io.serverlessworkflow.api.Workflow;
 import io.serverlessworkflow.api.actions.Action;
 import io.serverlessworkflow.api.events.EventDefinition;
@@ -27,15 +28,14 @@
 import io.serverlessworkflow.api.states.*;
 import io.serverlessworkflow.api.switchconditions.DataCondition;
 import io.serverlessworkflow.api.switchconditions.EventCondition;
+import io.serverlessworkflow.api.utils.Utils;
 import io.serverlessworkflow.api.validation.ValidationError;
 import io.serverlessworkflow.api.validation.WorkflowSchemaLoader;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import org.everit.json.schema.Schema;
-import org.everit.json.schema.ValidationException;
-import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,7 +44,7 @@ public class WorkflowValidatorImpl implements WorkflowValidator {
   private static final Logger logger = LoggerFactory.getLogger(WorkflowValidatorImpl.class);
   private boolean schemaValidationEnabled = true;
   private List<ValidationError> validationErrors = new ArrayList<>();
-  private Schema workflowSchema = WorkflowSchemaLoader.getWorkflowSchema();
+  private JsonNode workflowSchema = WorkflowSchemaLoader.getWorkflowSchema();
   private String source;
   private Workflow workflow;
 
@@ -66,34 +66,13 @@ public List<ValidationError> validate() {
     if (workflow == null) {
       try {
         if (schemaValidationEnabled && source != null) {
-          try {
-            if (!source.trim().startsWith("{")) {
-              // convert yaml to json to validate
-              ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
-              Object obj = yamlReader.readValue(source, Object.class);
-
-              ObjectMapper jsonWriter = new ObjectMapper();
-
-              workflowSchema.validate(new JSONObject(jsonWriter.writeValueAsString(obj)));
-            } else {
-              workflowSchema.validate(new JSONObject(source));
-            }
-          } catch (ValidationException e) {
-            e.getCausingExceptions().stream()
-                .map(ValidationException::getMessage)
-                .forEach(
-                    m -> {
-                      if ((!m.equals("#/functions: expected type: JSONObject, found: JSONArray")
-                          && !m.equals("#/events: expected type: JSONObject, found: JSONArray")
-                          && !m.equals("#/start: expected type: JSONObject, found: String")
-                          && !m.equals("#/retries: expected type: JSONObject, found: JSONArray"))) {
-                        addValidationError(m, ValidationError.SCHEMA_VALIDATION);
-                      }
-                    });
-          }
+          JsonSchemaFactory.getInstance(VersionFlag.V7)
+              .getSchema(workflowSchema)
+              .validate(Utils.getNode(source))
+              .forEach(m -> addValidationError(m.getMessage(), ValidationError.SCHEMA_VALIDATION));
         }
-      } catch (Exception e) {
-        logger.error("Schema validation exception: " + e.getMessage());
+      } catch (IOException e) {
+        logger.error("Unexpected error during validation", e);
       }
     }
 
@@ -101,42 +80,34 @@ public List<ValidationError> validate() {
     // there is no point of doing the workflow validation
     if (validationErrors.size() > 0) {
       return validationErrors;
-    } else {
-      if (workflow == null) {
-        workflow = Workflow.fromSource(source);
-      }
-
-      List<FunctionDefinition> functions =
-          workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null;
+    } else if (workflow == null) {
+      workflow = Workflow.fromSource(source);
+    }
 
-      List<EventDefinition> events =
-          workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null;
+    List<FunctionDefinition> functions =
+        workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null;
 
-      if (workflow.getId() == null || workflow.getId().trim().isEmpty()) {
-        addValidationError("Workflow id should not be empty", ValidationError.WORKFLOW_VALIDATION);
-      }
+    List<EventDefinition> events =
+        workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null;
 
-      if (workflow.getName() == null || workflow.getName().trim().isEmpty()) {
-        addValidationError(
-            "Workflow name should not be empty", ValidationError.WORKFLOW_VALIDATION);
-      }
-
-      if (workflow.getStart() == null) {
-        addValidationError(
-            "Workflow must define a starting state", ValidationError.WORKFLOW_VALIDATION);
-      }
+    if ((workflow.getId() == null || workflow.getId().trim().isEmpty())
+        && (workflow.getKey() == null || workflow.getKey().trim().isEmpty())) {
+      addValidationError(
+          "Workflow id or key should not be empty", ValidationError.WORKFLOW_VALIDATION);
+    }
 
-      if (workflow.getVersion() == null || workflow.getVersion().trim().isEmpty()) {
-        addValidationError(
-            "Workflow version should not be empty", ValidationError.WORKFLOW_VALIDATION);
-      }
+    if (workflow.getVersion() == null || workflow.getVersion().trim().isEmpty()) {
+      addValidationError(
+          "Workflow version should not be empty", ValidationError.WORKFLOW_VALIDATION);
+    }
 
-      if (workflow.getStates() == null || workflow.getStates().isEmpty()) {
-        addValidationError("No states found", ValidationError.WORKFLOW_VALIDATION);
-      }
+    if (workflow.getStates() == null || workflow.getStates().isEmpty()) {
+      addValidationError("No states found", ValidationError.WORKFLOW_VALIDATION);
+    }
 
-      if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
-        boolean existingStateWithStartProperty = false;
+    if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
+      boolean existingStateWithStartProperty = false;
+      if (workflow.getStart() != null) {
         String startProperty = workflow.getStart().getStateName();
         for (State s : workflow.getStates()) {
           if (s.getName().equals(startProperty)) {
@@ -144,226 +115,209 @@ public List<ValidationError> validate() {
             break;
           }
         }
-        if (!existingStateWithStartProperty) {
-          addValidationError(
-              "No state name found that matches the workflow start definition",
-              ValidationError.WORKFLOW_VALIDATION);
-        }
+      } else {
+        existingStateWithStartProperty = true;
       }
+      if (!existingStateWithStartProperty) {
+        addValidationError(
+            "No state name found that matches the workflow start definition",
+            ValidationError.WORKFLOW_VALIDATION);
+      }
+    }
 
-      Validation validation = new Validation();
-      if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
-        workflow
-            .getStates()
-            .forEach(
-                s -> {
-                  if (s.getName() != null && s.getName().trim().isEmpty()) {
-                    addValidationError(
-                        "State name should not be empty", ValidationError.WORKFLOW_VALIDATION);
-                  } else {
-                    validation.addState(s.getName());
-                  }
-
-                  if (s.getEnd() != null) {
-                    validation.addEndState();
-                  }
-
-                  if (s instanceof OperationState) {
-                    OperationState operationState = (OperationState) s;
-
-                    List<Action> actions = operationState.getActions();
-                    for (Action action : actions) {
-                      if (action.getFunctionRef() != null) {
-                        if (action.getFunctionRef().getRefName().isEmpty()) {
-                          addValidationError(
-                              "Operation State action functionRef should not be null or empty",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
-
-                        if (!haveFunctionDefinition(
-                            action.getFunctionRef().getRefName(), functions)) {
-                          addValidationError(
-                              "Operation State action functionRef does not reference an existing workflow function definition",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
+    Validation validation = new Validation();
+    if (workflow.getStates() != null && !workflow.getStates().isEmpty()) {
+      workflow
+          .getStates()
+          .forEach(
+              s -> {
+                if (s.getName() != null && s.getName().trim().isEmpty()) {
+                  addValidationError(
+                      "State name should not be empty", ValidationError.WORKFLOW_VALIDATION);
+                } else {
+                  validation.addState(s.getName());
+                }
+
+                if (s.getEnd() != null) {
+                  validation.addEndState();
+                }
+
+                if (s instanceof OperationState) {
+                  OperationState operationState = (OperationState) s;
+
+                  List<Action> actions = operationState.getActions();
+                  for (Action action : actions) {
+                    if (action.getFunctionRef() != null) {
+                      if (action.getFunctionRef().getRefName().isEmpty()) {
+                        addValidationError(
+                            "Operation State action functionRef should not be null or empty",
+                            ValidationError.WORKFLOW_VALIDATION);
                       }
 
-                      if (action.getEventRef() != null) {
-                        if (action.getEventRef().getTriggerEventRef().isEmpty()) {
-                          addValidationError(
-                              "Operation State action trigger eventRef does not reference an existing workflow event definition",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
-
-                        if (action.getEventRef().getResultEventRef().isEmpty()) {
-                          addValidationError(
-                              "Operation State action results eventRef does not reference an existing workflow event definition",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
-
-                        if (!haveEventsDefinition(
-                            action.getEventRef().getTriggerEventRef(), events)) {
-                          addValidationError(
-                              "Operation State action trigger event def does not reference an existing workflow event definition",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
-
-                        if (!haveEventsDefinition(
-                            action.getEventRef().getResultEventRef(), events)) {
-                          addValidationError(
-                              "Operation State action results event def does not reference an existing workflow event definition",
-                              ValidationError.WORKFLOW_VALIDATION);
-                        }
+                      if (!haveFunctionDefinition(
+                          action.getFunctionRef().getRefName(), functions)) {
+                        addValidationError(
+                            "Operation State action functionRef does not reference an existing workflow function definition",
+                            ValidationError.WORKFLOW_VALIDATION);
                       }
                     }
-                  }
 
-                  if (s instanceof EventState) {
-                    EventState eventState = (EventState) s;
-                    if (eventState.getOnEvents() == null || eventState.getOnEvents().size() < 1) {
-                      addValidationError(
-                          "Event State has no eventActions defined",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
-                    List<OnEvents> eventsActionsList = eventState.getOnEvents();
-                    for (OnEvents onEvents : eventsActionsList) {
+                    if (action.getEventRef() != null) {
 
-                      List<String> eventRefs = onEvents.getEventRefs();
-                      if (eventRefs == null || eventRefs.size() < 1) {
+                      if (!haveEventsDefinition(
+                          action.getEventRef().getTriggerEventRef(), events)) {
                         addValidationError(
-                            "Event State eventsActions has no event refs",
+                            "Operation State action trigger event def does not reference an existing workflow event definition",
+                            ValidationError.WORKFLOW_VALIDATION);
+                      }
+
+                      if (!haveEventsDefinition(action.getEventRef().getResultEventRef(), events)) {
+                        addValidationError(
+                            "Operation State action results event def does not reference an existing workflow event definition",
                             ValidationError.WORKFLOW_VALIDATION);
-                      } else {
-                        for (String eventRef : eventRefs) {
-                          if (!haveEventsDefinition(eventRef, events)) {
-                            addValidationError(
-                                "Event State eventsActions eventRef does not match a declared workflow event definition",
-                                ValidationError.WORKFLOW_VALIDATION);
-                          }
-                        }
                       }
                     }
                   }
+                }
 
-                  if (s instanceof SwitchState) {
-                    SwitchState switchState = (SwitchState) s;
-                    if ((switchState.getDataConditions() == null
-                            || switchState.getDataConditions().size() < 1)
-                        && (switchState.getEventConditions() == null
-                            || switchState.getEventConditions().size() < 1)) {
-                      addValidationError(
-                          "Switch state should define either data or event conditions",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                if (s instanceof EventState) {
+                  EventState eventState = (EventState) s;
+                  if (eventState.getOnEvents() == null || eventState.getOnEvents().size() < 1) {
+                    addValidationError(
+                        "Event State has no eventActions defined",
+                        ValidationError.WORKFLOW_VALIDATION);
+                  }
+                  List<OnEvents> eventsActionsList = eventState.getOnEvents();
+                  for (OnEvents onEvents : eventsActionsList) {
 
-                    if (switchState.getDefaultCondition() == null) {
+                    List<String> eventRefs = onEvents.getEventRefs();
+                    if (eventRefs == null || eventRefs.size() < 1) {
                       addValidationError(
-                          "Switch state should define a default transition",
+                          "Event State eventsActions has no event refs",
                           ValidationError.WORKFLOW_VALIDATION);
-                    }
-
-                    if (switchState.getEventConditions() != null
-                        && switchState.getEventConditions().size() > 0) {
-                      List<EventCondition> eventConditions = switchState.getEventConditions();
-                      for (EventCondition ec : eventConditions) {
-                        if (!haveEventsDefinition(ec.getEventRef(), events)) {
+                    } else {
+                      for (String eventRef : eventRefs) {
+                        if (!haveEventsDefinition(eventRef, events)) {
                           addValidationError(
-                              "Switch state event condition eventRef does not reference a defined workflow event",
+                              "Event State eventsActions eventRef does not match a declared workflow event definition",
                               ValidationError.WORKFLOW_VALIDATION);
                         }
-                        if (ec.getEnd() != null) {
-                          validation.addEndState();
-                        }
                       }
                     }
+                  }
+                }
+
+                if (s instanceof SwitchState) {
+                  SwitchState switchState = (SwitchState) s;
+                  if ((switchState.getDataConditions() == null
+                          || switchState.getDataConditions().size() < 1)
+                      && (switchState.getEventConditions() == null
+                          || switchState.getEventConditions().size() < 1)) {
+                    addValidationError(
+                        "Switch state should define either data or event conditions",
+                        ValidationError.WORKFLOW_VALIDATION);
+                  }
 
-                    if (switchState.getDataConditions() != null
-                        && switchState.getDataConditions().size() > 0) {
-                      List<DataCondition> dataConditions = switchState.getDataConditions();
-                      for (DataCondition dc : dataConditions) {
-                        if (dc.getEnd() != null) {
-                          validation.addEndState();
-                        }
+                  if (switchState.getDefaultCondition() == null) {
+                    addValidationError(
+                        "Switch state should define a default transition",
+                        ValidationError.WORKFLOW_VALIDATION);
+                  }
+
+                  if (switchState.getEventConditions() != null
+                      && switchState.getEventConditions().size() > 0) {
+                    List<EventCondition> eventConditions = switchState.getEventConditions();
+                    for (EventCondition ec : eventConditions) {
+                      if (!haveEventsDefinition(ec.getEventRef(), events)) {
+                        addValidationError(
+                            "Switch state event condition eventRef does not reference a defined workflow event",
+                            ValidationError.WORKFLOW_VALIDATION);
+                      }
+                      if (ec.getEnd() != null) {
+                        validation.addEndState();
                       }
                     }
                   }
 
-                  if (s instanceof SleepState) {
-                    SleepState sleepState = (SleepState) s;
-                    if (sleepState.getDuration() == null || sleepState.getDuration().length() < 1) {
-                      addValidationError(
-                          "Sleep state should have a non-empty time delay",
-                          ValidationError.WORKFLOW_VALIDATION);
+                  if (switchState.getDataConditions() != null
+                      && switchState.getDataConditions().size() > 0) {
+                    List<DataCondition> dataConditions = switchState.getDataConditions();
+                    for (DataCondition dc : dataConditions) {
+                      if (dc.getEnd() != null) {
+                        validation.addEndState();
+                      }
                     }
                   }
+                }
 
-                  if (s instanceof ParallelState) {
-                    ParallelState parallelState = (ParallelState) s;
-
-                    if (parallelState.getBranches() == null
-                        || parallelState.getBranches().size() < 2) {
-                      addValidationError(
-                          "Parallel state should have at lest two branches",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                if (s instanceof SleepState) {
+                  SleepState sleepState = (SleepState) s;
+                  if (sleepState.getDuration() == null || sleepState.getDuration().length() < 1) {
+                    addValidationError(
+                        "Sleep state should have a non-empty time delay",
+                        ValidationError.WORKFLOW_VALIDATION);
                   }
+                }
 
-                  if (s instanceof InjectState) {
-                    InjectState injectState = (InjectState) s;
-                    if (injectState.getData() == null || injectState.getData().isEmpty()) {
-                      addValidationError(
-                          "InjectState should have non-null data",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                if (s instanceof ParallelState) {
+                  ParallelState parallelState = (ParallelState) s;
+
+                  if (parallelState.getBranches() == null
+                      || parallelState.getBranches().size() < 2) {
+                    addValidationError(
+                        "Parallel state should have at lest two branches",
+                        ValidationError.WORKFLOW_VALIDATION);
                   }
+                }
 
-                  if (s instanceof ForEachState) {
-                    ForEachState forEachState = (ForEachState) s;
-                    if (forEachState.getInputCollection() == null
-                        || forEachState.getInputCollection().isEmpty()) {
-                      addValidationError(
-                          "ForEach state should have a valid inputCollection",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                if (s instanceof InjectState) {
+                  InjectState injectState = (InjectState) s;
+                  if (injectState.getData() == null || injectState.getData().isEmpty()) {
+                    addValidationError(
+                        "InjectState should have non-null data",
+                        ValidationError.WORKFLOW_VALIDATION);
+                  }
+                }
 
-                    if (forEachState.getIterationParam() == null
-                        || forEachState.getIterationParam().isEmpty()) {
-                      addValidationError(
-                          "ForEach state should have a valid iteration parameter",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                if (s instanceof ForEachState) {
+                  ForEachState forEachState = (ForEachState) s;
+                  if (forEachState.getInputCollection() == null
+                      || forEachState.getInputCollection().isEmpty()) {
+                    addValidationError(
+                        "ForEach state should have a valid inputCollection",
+                        ValidationError.WORKFLOW_VALIDATION);
                   }
+                }
 
-                  if (s instanceof CallbackState) {
-                    CallbackState callbackState = (CallbackState) s;
+                if (s instanceof CallbackState) {
+                  CallbackState callbackState = (CallbackState) s;
 
-                    if (!haveEventsDefinition(callbackState.getEventRef(), events)) {
-                      addValidationError(
-                          "CallbackState event ref does not reference a defined workflow event definition",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                  if (!haveEventsDefinition(callbackState.getEventRef(), events)) {
+                    addValidationError(
+                        "CallbackState event ref does not reference a defined workflow event definition",
+                        ValidationError.WORKFLOW_VALIDATION);
+                  }
 
-                    if (haveFunctionDefinition(
-                        callbackState.getAction().getFunctionRef().getRefName(), functions)) {
-                      addValidationError(
-                          "CallbackState action function ref does not reference a defined workflow function definition",
-                          ValidationError.WORKFLOW_VALIDATION);
-                    }
+                  if (!haveFunctionDefinition(
+                      callbackState.getAction().getFunctionRef().getRefName(), functions)) {
+                    addValidationError(
+                        "CallbackState action function ref does not reference a defined workflow function definition",
+                        ValidationError.WORKFLOW_VALIDATION);
                   }
-                });
+                }
+              });
 
-        if (validation.endStates == 0) {
-          addValidationError("No end state found.", ValidationError.WORKFLOW_VALIDATION);
-        }
+      if (validation.endStates == 0) {
+        addValidationError("No end state found.", ValidationError.WORKFLOW_VALIDATION);
       }
-
-      return validationErrors;
     }
+
+    return validationErrors;
   }
 
   @Override
   public boolean isValid() {
-    return validate().size() < 1;
+    return validate().isEmpty();
   }
 
   @Override
@@ -392,17 +346,30 @@ private boolean haveFunctionDefinition(String functionName, List<FunctionDefinit
   }
 
   private boolean haveEventsDefinition(String eventName, List<EventDefinition> events) {
+    if (eventName == null) {
+      return true;
+    }
     if (events != null) {
       EventDefinition eve =
           events.stream().filter(e -> e.getName().equals(eventName)).findFirst().orElse(null);
-
       return eve == null ? false : true;
     } else {
       return false;
     }
   }
 
+  private static final Set<String> skipMessages =
+      new HashSet<String>() {
+        {
+          add("$.start: string found, object expected");
+          add("$.functions: array found, object expected");
+        }
+      };
+
   private void addValidationError(String message, String type) {
+    if (skipMessages.contains(message)) {
+      return;
+    }
     ValidationError mainError = new ValidationError();
     mainError.setMessage(message);
     mainError.setType(type);
@@ -410,30 +377,9 @@ private void addValidationError(String message, String type) {
   }
 
   private class Validation {
-
-    final Set<String> events = new HashSet<>();
-    final Set<String> functions = new HashSet<>();
     final Set<String> states = new HashSet<>();
     Integer endStates = 0;
 
-    void addFunction(String name) {
-      if (functions.contains(name)) {
-        addValidationError(
-            "Function does not have an unique name: " + name, ValidationError.WORKFLOW_VALIDATION);
-      } else {
-        functions.add(name);
-      }
-    }
-
-    void addEvent(String name) {
-      if (events.contains(name)) {
-        addValidationError(
-            "Event does not have an unique name: " + name, ValidationError.WORKFLOW_VALIDATION);
-      } else {
-        events.add(name);
-      }
-    }
-
     void addState(String name) {
       if (states.contains(name)) {
         addValidationError(
diff --git a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java
index 8baf9dfa..6ccef44f 100644
--- a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java
+++ b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java
@@ -15,14 +15,26 @@
  */
 package io.serverlessworkflow.validation.test;
 
+import static io.serverlessworkflow.api.states.DefaultState.Type.OPERATION;
 import static io.serverlessworkflow.api.states.DefaultState.Type.SLEEP;
 
 import io.serverlessworkflow.api.Workflow;
+import io.serverlessworkflow.api.actions.Action;
 import io.serverlessworkflow.api.end.End;
+import io.serverlessworkflow.api.events.EventDefinition;
+import io.serverlessworkflow.api.events.EventRef;
+import io.serverlessworkflow.api.functions.FunctionDefinition;
+import io.serverlessworkflow.api.functions.FunctionDefinition.Type;
+import io.serverlessworkflow.api.functions.FunctionRef;
 import io.serverlessworkflow.api.interfaces.WorkflowValidator;
+import io.serverlessworkflow.api.retry.RetryDefinition;
 import io.serverlessworkflow.api.start.Start;
+import io.serverlessworkflow.api.states.OperationState;
 import io.serverlessworkflow.api.states.SleepState;
 import io.serverlessworkflow.api.validation.ValidationError;
+import io.serverlessworkflow.api.workflow.Events;
+import io.serverlessworkflow.api.workflow.Functions;
+import io.serverlessworkflow.api.workflow.Retries;
 import io.serverlessworkflow.validation.WorkflowValidatorImpl;
 import java.util.Arrays;
 import java.util.List;
@@ -44,9 +56,9 @@ public void testIncompleteJsonWithSchemaValidation() {
   public void testIncompleteYamlWithSchemaValidation() {
     WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
     List<ValidationError> validationErrors =
-        workflowValidator.setSource("---\n" + "id: abc\n").validate();
+        workflowValidator.setSource("---\n" + "key: abc\n").validate();
     Assertions.assertNotNull(validationErrors);
-    Assertions.assertEquals(3, validationErrors.size());
+    Assertions.assertEquals(4, validationErrors.size());
   }
 
   @Test
@@ -67,13 +79,10 @@ public void testFromIncompleteWorkflow() {
     WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
     List<ValidationError> validationErrors = workflowValidator.setWorkflow(workflow).validate();
     Assertions.assertNotNull(validationErrors);
-    Assertions.assertEquals(2, validationErrors.size());
-
-    Assertions.assertEquals(
-        "Workflow name should not be empty", validationErrors.get(0).getMessage());
+    Assertions.assertEquals(1, validationErrors.size());
     Assertions.assertEquals(
         "No state name found that matches the workflow start definition",
-        validationErrors.get(1).getMessage());
+        validationErrors.get(0).getMessage());
   }
 
   @Test
@@ -96,6 +105,26 @@ public void testWorkflowMissingStates() {
     Assertions.assertEquals("No states found", validationErrors.get(0).getMessage());
   }
 
+  @Test
+  public void testWorkflowMissingStatesIdAndKey() {
+    WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
+    List<ValidationError> validationErrors =
+        workflowValidator
+            .setSource(
+                "{\n"
+                    + "\t\"name\": \"test workflow\",\n"
+                    + "  \"version\": \"1.0\",\n"
+                    + "  \"start\": \"SomeState\",\n"
+                    + "  \"states\": []\n"
+                    + "}")
+            .validate();
+    Assertions.assertNotNull(validationErrors);
+    Assertions.assertEquals(1, validationErrors.size());
+
+    Assertions.assertEquals(
+        "$: required property 'id' not found", validationErrors.get(0).getMessage());
+  }
+
   @Test
   public void testOperationStateNoFunctionRef() {
     WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
@@ -104,7 +133,7 @@ public void testOperationStateNoFunctionRef() {
             .setSource(
                 "{\n"
                     + "\"id\": \"checkInbox\",\n"
-                    + "  \"name\": \"Check Inbox Workflow\",\n"
+                    + "\"name\": \"Check Inbox Workflow\",\n"
                     + "\"description\": \"Periodically Check Inbox\",\n"
                     + "\"version\": \"1.0\",\n"
                     + "\"start\": \"CheckInbox\",\n"
@@ -147,4 +176,161 @@ public void testOperationStateNoFunctionRef() {
         "Operation State action functionRef does not reference an existing workflow function definition",
         validationErrors.get(0).getMessage());
   }
+
+  @Test
+  public void testValidateWorkflowForOptionalStartStateAndWorkflowName() {
+    Workflow workflow =
+        new Workflow()
+            .withId("test-workflow")
+            .withVersion("1.0")
+            .withStates(
+                Arrays.asList(
+                    new SleepState()
+                        .withName("sleepState")
+                        .withType(SLEEP)
+                        .withEnd(new End())
+                        .withDuration("PT1M")));
+
+    WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
+    List<ValidationError> validationErrors = workflowValidator.setWorkflow(workflow).validate();
+    Assertions.assertNotNull(validationErrors);
+    Assertions.assertEquals(0, validationErrors.size());
+  }
+
+  @Test
+  public void testValidateWorkflowForOptionalIterationParam() {
+    WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
+    List<ValidationError> validationErrors =
+        workflowValidator
+            .setSource(
+                "{\n"
+                    + "\"id\": \"checkInbox\",\n"
+                    + "  \"name\": \"Check Inbox Workflow\",\n"
+                    + "\"description\": \"Periodically Check Inbox\",\n"
+                    + "\"version\": \"1.0\",\n"
+                    + "\"start\": \"CheckInbox\",\n"
+                    + "\"functions\": [\n"
+                    + "\n"
+                    + "],\n"
+                    + "\"states\": [\n"
+                    + "    {\n"
+                    + "        \"name\": \"CheckInbox\",\n"
+                    + "        \"type\": \"operation\",\n"
+                    + "        \"actionMode\": \"sequential\",\n"
+                    + "        \"actions\": [\n"
+                    + "            {\n"
+                    + "                \"functionRef\": {\n"
+                    + "                    \"refName\": \"checkInboxFunction\"\n"
+                    + "                }\n"
+                    + "            }\n"
+                    + "        ],\n"
+                    + "        \"transition\": {\n"
+                    + "            \"nextState\": \"SendTextForHighPrioriry\"\n"
+                    + "        }\n"
+                    + "    },\n"
+                    + "    {\n"
+                    + "        \"name\": \"SendTextForHighPrioriry\",\n"
+                    + "        \"type\": \"foreach\",\n"
+                    + "        \"inputCollection\": \"${ .message }\",\n"
+                    + "        \"end\": {\n"
+                    + "            \"kind\": \"default\"\n"
+                    + "        }\n"
+                    + "    }\n"
+                    + "]\n"
+                    + "}")
+            .validate();
+
+    Assertions.assertNotNull(validationErrors);
+    Assertions.assertEquals(
+        1,
+        validationErrors.size()); // validation error raised for functionref not for iterationParam
+  }
+
+  @Test
+  public void testMissingFunctionRefForCallbackState() {
+    WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
+    List<ValidationError> validationErrors =
+        workflowValidator
+            .setSource(
+                "{\n"
+                    + "  \"id\": \"callbackstatemissingfuncref\",\n"
+                    + "  \"version\": \"1.0\",\n"
+                    + "  \"specVersion\": \"0.8\",\n"
+                    + "  \"name\": \"Callback State Test\",\n"
+                    + "  \"start\": \"CheckCredit\",\n"
+                    + "  \"states\": [\n"
+                    + "    {\n"
+                    + "      \"name\": \"CheckCredit\",\n"
+                    + "      \"type\": \"callback\",\n"
+                    + "      \"action\": {\n"
+                    + "        \"functionRef\": {\n"
+                    + "          \"refName\": \"callCreditCheckMicroservice\",\n"
+                    + "          \"arguments\": {\n"
+                    + "            \"customer\": \"${ .customer }\"\n"
+                    + "          }\n"
+                    + "        }\n"
+                    + "      },\n"
+                    + "      \"eventRef\": \"CreditCheckCompletedEvent\",\n"
+                    + "      \"timeouts\": {\n"
+                    + "        \"stateExecTimeout\": \"PT15M\"\n"
+                    + "      },\n"
+                    + "      \"end\": true\n"
+                    + "    }\n"
+                    + "  ]\n"
+                    + "}")
+            .validate();
+
+    Assertions.assertNotNull(validationErrors);
+    Assertions.assertEquals(2, validationErrors.size());
+    Assertions.assertEquals(
+        "CallbackState event ref does not reference a defined workflow event definition",
+        validationErrors.get(0).getMessage());
+    Assertions.assertEquals(
+        "CallbackState action function ref does not reference a defined workflow function definition",
+        validationErrors.get(1).getMessage());
+  }
+
+  @Test
+  void testFunctionCall() {
+    Workflow workflow =
+        new Workflow()
+            .withId("test-workflow")
+            .withVersion("1.0")
+            .withStart(new Start().withStateName("start"))
+            .withFunctions(
+                new Functions(
+                    Arrays.asList(new FunctionDefinition("expression").withType(Type.EXPRESSION))))
+            .withStates(
+                Arrays.asList(
+                    new OperationState()
+                        .withName("start")
+                        .withType(OPERATION)
+                        .withActions(
+                            Arrays.asList(
+                                new Action().withFunctionRef(new FunctionRef("expression"))))
+                        .withEnd(new End())));
+    Assertions.assertTrue(new WorkflowValidatorImpl().setWorkflow(workflow).validate().isEmpty());
+  }
+
+  @Test
+  void testEventCall() {
+    Workflow workflow =
+        new Workflow()
+            .withId("test-workflow")
+            .withVersion("1.0")
+            .withStart(new Start().withStateName("start"))
+            .withEvents(new Events(Arrays.asList(new EventDefinition().withName("event"))))
+            .withRetries(new Retries(Arrays.asList(new RetryDefinition("start", "PT1S"))))
+            .withStates(
+                Arrays.asList(
+                    new OperationState()
+                        .withName("start")
+                        .withType(OPERATION)
+                        .withActions(
+                            Arrays.asList(
+                                new Action()
+                                    .withEventRef(new EventRef().withTriggerEventRef("event"))))
+                        .withEnd(new End())));
+    Assertions.assertTrue(new WorkflowValidatorImpl().setWorkflow(workflow).validate().isEmpty());
+  }
 }
