Repository: networknt/json-schema-validator
Pull request #1058 (2024-06-11 09:03:05)

Add GraalJS regular expression and factory implementation
This adds the optional usage of GraalJS as the regular expression evaluation engine. This makes it somewhat easier to verify that the Joni regular expression implementation is correct.

This also fixes the JoniRegularExpression implementation as the `Syntax.ECMAScript` wasn't accepting certain valid regular expressions by configuring a custom `Syntax` with additional options
* [OP2_QMARK_LT_NAMED_GROUP](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group)
* [OP2_ESC_K_NAMED_BACKREF](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_backreference)

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) Report
Attention: Patch coverage is `93.22034%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 78.86%. Comparing base [(`48ca3c2`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/48ca3c22995e32797e9defdb8b77869f0d15caf3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) to head [(`3e41dee`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/3e41dee7540a17c2f9381af8a8757ce498dfa1ce?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).
> Report is 21 commits behind head on master.

| [Files](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) | Patch % | Lines |
|---|---|---|
| [...hema/regex/ECMAScriptRegularExpressionFactory.java](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Fnetworknt%2Fschema%2Fregex%2FECMAScriptRegularExpressionFactory.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9yZWdleC9FQ01BU2NyaXB0UmVndWxhckV4cHJlc3Npb25GYWN0b3J5LmphdmE=) | 76.92% | [1 Missing and 2 partials :warning: ](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) |
| [.../networknt/schema/regex/GraalJSContextFactory.java](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Fnetworknt%2Fschema%2Fregex%2FGraalJSContextFactory.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9yZWdleC9HcmFhbEpTQ29udGV4dEZhY3RvcnkuamF2YQ==) | 75.00% | [1 Missing :warning: ](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #1058      +/-   ##
============================================
- Coverage     78.90%   78.86%   -0.05%     
- Complexity     1965     2061      +96     
============================================
  Files           172      198      +26     
  Lines          6352     6651     +299     
  Branches       1255     1261       +6     
============================================
+ Hits           5012     5245     +233     
- Misses          867      929      +62     
- Partials        473      477       +4     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1058?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).


Patch:
diff --git a/README.md b/README.md
index 6e0323d..cfb0f92 100644
--- a/README.md
+++ b/README.md
@@ -157,14 +157,26 @@ The following are the optional dependencies that may be required for certain opt
 These are not automatically included and setting the relevant option without adding the library will result in a `ClassNotFoundException`.
 
 ```xml
-<!-- This is required when setting setEcma262Validator(true)  -->
+<!-- Either library is required when setting setEcma262Validator(true) or explicitly via setRegularExpressionFactory() -->
 <dependency>
     <!-- Used to validate ECMA 262 regular expressions -->
+    <!-- Approximately 2 MB in dependencies -->
+    <!-- JoniRegularExpressionFactory -->
     <groupId>org.jruby.joni</groupId>
     <artifactId>joni</artifactId>
     <version>${version.joni}</version>
     <optional>true</optional>
 </dependency>
+
+<dependency>
+    <!-- Used to validate ECMA 262 regular expressions -->
+    <!-- Approximately 50 MB in dependencies -->
+    <!-- GraalJSRegularExpressionFactory -->
+    <groupId>org.graalvm.js</groupId>
+    <artifactId>js</artifactId>
+    <version>${version.graaljs}</version>
+    <optional>true</optional>
+</dependency>
 ```
 
 ##### Excludable Dependencies
@@ -258,7 +270,7 @@ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
 // By default JSON Path is used for reporting the instance location and evaluation path
 config.setPathType(PathType.JSON_POINTER);
 // By default the JDK regular expression implementation which is not ECMA 262 compliant is used
-// Note that setting this to true requires including the optional joni dependency
+// Note that setting this to true requires including the optional joni or graaljs dependency
 // config.setEcma262Validator(true);
 
 // Due to the mapping the schema will be retrieved from the classpath at classpath:schema/example-main.json.
@@ -293,7 +305,7 @@ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
 // By default JSON Path is used for reporting the instance location and evaluation path
 config.setPathType(PathType.JSON_POINTER);
 // By default the JDK regular expression implementation which is not ECMA 262 compliant is used
-// Note that setting this to true requires including the optional joni dependency
+// Note that setting this to true requires including the optional joni or graaljs dependency
 // config.setEcma262Validator(true);
 
 // Due to the mapping the meta-schema will be retrieved from the classpath at classpath:draft/2020-12/schema.
@@ -510,13 +522,14 @@ The following is sample output from the Hierarchical format.
 | `locale`                       | The locale to use for generating messages in the `ValidationMessage`. Note that this value is copied from `SchemaValidatorsConfig` for each execution.                                                                            | `Locale.getDefault()`
 | `failFast`                     | Whether to return failure immediately when an assertion is generated. Note that this value is copied from `SchemaValidatorsConfig` for each execution but is automatically set to `true` for the Boolean and Flag output formats. | `false`
 | `formatAssertionsEnabled`      | The default is to generate format assertions from Draft 4 to Draft 7 and to only generate annotations from Draft 2019-09. Setting to `true` or `false` will override the default behavior.                                        | `null`
+| `debugEnabled`                 | Controls whether debug logging is enabled for logging the node information when processing. Note that this will generate a lot of logs that will affect performance.                                                              | `false`
 
 ### Schema Validators Configuration
 
 | Name                                  | Description                                                                                                                                                                                                                       | Default Value
 |---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------
 | `pathType`                            | The path type to use for reporting the instance location and evaluation path. Set to `PathType.JSON_POINTER` to use JSON Pointer.                                                                                                 | `PathType.DEFAULT`
-| `ecma262Validator`                    | Whether to use the ECMA 262 `joni` library to validate the `pattern` keyword. This requires the dependency to be manually added to the project or a `ClassNotFoundException` will be thrown.                                      | `false`
+| `ecma262Validator`                    | Whether to use the ECMA 262 `joni` or `graaljs` library to validate the `pattern` keyword. This requires the dependency to be manually added to the project or a `ClassNotFoundException` will be thrown.                         | `false`
 | `executionContextCustomizer`          | This can be used to customize the `ExecutionContext` generated by the `JsonSchema` for each validation run.                                                                                                                       | `null`
 | `schemaIdValidator`                   | This is used to customize how the `$id` values are validated. Note that the default implementation allows non-empty fragments where no base IRI is specified and also allows non-absolute IRI `$id` values in the root schema.    | `JsonSchemaIdValidator.DEFAULT`
 | `messageSource`                       | This is used to retrieve the locale specific messages.                                                                                                                                                                            | `DefaultMessageSource.getInstance()`
@@ -526,6 +539,7 @@ The following is sample output from the Hierarchical format.
 | `locale`                              | The locale to use for generating messages in the `ValidationMessage`.                                                                                                                                                             | `Locale.getDefault()`
 | `failFast`                            | Whether to return failure immediately when an assertion is generated.                                                                                                                                                             | `false`
 | `formatAssertionsEnabled`             | The default is to generate format assertions from Draft 4 to Draft 7 and to only generate annotations from Draft 2019-09. Setting to `true` or `false` will override the default behavior.                                        | `null`
+| `regularExpressionFactory`            | The factory to use to create regular expressions for instance `JoniRegularExpressionFactory` or `GraalJSRegularExpressionFactory`.                                                                                                | `JDKRegularExpressionFactory.getInstance()`
 
 ## Performance Considerations
 
diff --git a/pom.xml b/pom.xml
index 1959b65..1cc1393 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,8 +75,9 @@
     <version.itu>1.10.2</version.itu>
     <version.jackson>2.17.0</version.jackson>
     <version.joni>2.2.1</version.joni>
-    <version.logback>1.3.14</version.logback>
+    <version.logback>1.3.14</version.logback> <!-- 1.4.x and above is not Java 8 compatible -->
     <version.slf4j>2.0.13</version.slf4j>
+    <version.graaljs>21.3.10</version.graaljs> <!-- 22.x and above is not Java 8 compatible -->
 
     <version.hamcrest>2.2</version.hamcrest>
     <version.junit>5.10.2</version.junit>
@@ -138,12 +139,31 @@
 
     <dependency>
       <!-- Used to validate ECMA 262 regular expressions -->
+      <!-- Approximately 2 MB in dependencies -->
       <groupId>org.jruby.joni</groupId>
       <artifactId>joni</artifactId>
       <version>${version.joni}</version>
       <optional>true</optional>
     </dependency>
 
+    <dependency>
+      <!-- Used to validate ECMA 262 regular expressions -->
+      <!-- Approximately 50 MB in dependencies -->
+      <groupId>org.graalvm.js</groupId>
+      <artifactId>js</artifactId>
+      <version>${version.graaljs}</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <!-- Used to validate ECMA 262 regular expressions -->
+      <!-- Approximately 50 MB in dependencies -->
+      <groupId>org.graalvm.sdk</groupId>
+      <artifactId>graal-sdk</artifactId>
+      <version>${version.graaljs}</version>
+      <optional>true</optional>
+    </dependency>
+
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
index 48b5f7d..d2c21f9 100644
--- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -80,8 +80,15 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
         }
     }
 
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+    @Override
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         if (!node.isObject()) {
             // ignore no object
             return Collections.emptySet();
@@ -110,15 +117,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
             if (pname.startsWith("#")) {
                 continue;
             }
-            boolean handledByPatternProperties = false;
-            for (RegularExpression pattern : patternProperties) {
-                if (pattern.matches(pname)) {
-                    handledByPatternProperties = true;
-                    break;
-                }
-            }
-
-            if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+            if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) {
                 if (!allowAdditionalProperties) {
                     if (errors == null) {
                         errors = new LinkedHashSet<>();
@@ -129,26 +128,16 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
                             .failFast(executionContext.isFailFast()).arguments(pname).build());
                 } else {
                     if (additionalPropertiesSchema != null) {
-                        ValidatorState state = executionContext.getValidatorState();
-                        if (state != null && state.isWalkEnabled()) {
-                            Set<ValidationMessage> results = additionalPropertiesSchema.walk(executionContext,
-                                    entry.getValue(), rootNode, instanceLocation.append(pname),
-                                    state.isValidationEnabled());
-                            if (!results.isEmpty()) {
-                                if (errors == null) {
-                                    errors = new LinkedHashSet<>();
-                                }
-                                errors.addAll(results);
-                            }
-                        } else {
-                            Set<ValidationMessage> results = additionalPropertiesSchema.validate(executionContext,
-                                    entry.getValue(), rootNode, instanceLocation.append(pname));
-                            if (!results.isEmpty()) {
-                                if (errors == null) {
-                                    errors = new LinkedHashSet<>();
-                                }
-                                errors.addAll(results);
+                        Set<ValidationMessage> results = !walk
+                                ? additionalPropertiesSchema.validate(executionContext, entry.getValue(), rootNode,
+                                        instanceLocation.append(pname))
+                                : additionalPropertiesSchema.walk(executionContext, entry.getValue(), rootNode,
+                                        instanceLocation.append(pname), true);
+                        if (!results.isEmpty()) {
+                            if (errors == null) {
+                                errors = new LinkedHashSet<>();
                             }
+                            errors.addAll(results);
                         }
                     }
                 }
@@ -166,7 +155,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema && node != null) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
 
         if (node == null || !node.isObject()) {
@@ -181,21 +170,11 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
             if (pname.startsWith("#")) {
                 continue;
             }
-            boolean handledByPatternProperties = false;
-            for (RegularExpression pattern : patternProperties) {
-                if (pattern.matches(pname)) {
-                    handledByPatternProperties = true;
-                    break;
-                }
-            }
-
-            if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+            if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) {
                 if (allowAdditionalProperties) {
                     if (additionalPropertiesSchema != null) {
-                        ValidatorState state = executionContext.getValidatorState();
-                        if (state != null && state.isWalkEnabled()) {
-                           additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode, instanceLocation.append(pname), state.isValidationEnabled());
-                        }
+                        additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode,
+                                instanceLocation.append(pname), shouldValidateSchema);
                     }
                 }
             }
@@ -203,6 +182,15 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
         return Collections.emptySet();
     }
 
+    private boolean handledByPatternProperties(String pname) {
+        for (RegularExpression pattern : this.patternProperties) {
+            if (pattern.matches(pname)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean collectAnnotations() {
         return hasUnevaluatedPropertiesValidator();
     }
diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java
index b451982..884930c 100644
--- a/src/main/java/com/networknt/schema/AllOfValidator.java
+++ b/src/main/java/com/networknt/schema/AllOfValidator.java
@@ -52,17 +52,18 @@ public class AllOfValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         SetView<ValidationMessage> childSchemaErrors = null;
 
         for (JsonSchema schema : this.schemas) {
             Set<ValidationMessage> localErrors = null;
 
-            if (!state.isWalkEnabled()) {
+            if (!walk) {
                 localErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
             } else {
                 localErrors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
@@ -111,7 +112,7 @@ public class AllOfValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemas) {
             // Walk through the schema
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
index 5dac253..5e9773b 100644
--- a/src/main/java/com/networknt/schema/AnyOfValidator.java
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -53,18 +53,18 @@ public class AnyOfValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
             executionContext.enterDiscriminatorContext(new DiscriminatorContext(), instanceLocation);
         }
-
-        boolean initialHasMatchedNode = state.hasMatchedNode();
-
         SetView<ValidationMessage> allErrors = null;
 
         int numberOfValidSubSchemas = 0;
@@ -75,8 +75,6 @@ public class AnyOfValidator extends BaseJsonValidator {
                 executionContext.setFailFast(false);
                 for (JsonSchema schema : this.schemas) {
                     Set<ValidationMessage> errors = Collections.emptySet();
-                    state.setMatchedNode(initialHasMatchedNode);
-
                     TypeValidator typeValidator = schema.getTypeValidator();
                     if (typeValidator != null) {
                         // If schema has type validator and node type doesn't match with schemaType then
@@ -90,7 +88,7 @@ public class AnyOfValidator extends BaseJsonValidator {
                             continue;
                         }
                     }
-                    if (!state.isWalkEnabled()) {
+                    if (!walk) {
                         errors = schema.validate(executionContext, node, rootNode, instanceLocation);
                     } else {
                         errors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
@@ -98,11 +96,6 @@ public class AnyOfValidator extends BaseJsonValidator {
 
                     // check if any validation errors have occurred
                     if (errors.isEmpty()) {
-                        // check whether there are no errors HOWEVER we have validated the exact
-                        // validator
-                        if (!state.hasMatchedNode()) {
-                            continue;
-                        }
                         // we found a valid subschema, so increase counter
                         numberOfValidSubSchemas++;
                     }
@@ -159,9 +152,6 @@ public class AnyOfValidator extends BaseJsonValidator {
             if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
                 executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
             }
-            if (allErrors == null || allErrors.isEmpty()) {
-                state.setMatchedNode(true);
-            }
         }
         if (numberOfValidSubSchemas >= 1) {
             return Collections.emptySet();
@@ -172,7 +162,7 @@ public class AnyOfValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemas) {
             schema.walk(executionContext, node, rootNode, instanceLocation, false);
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index eebcd9c..9576e6e 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -96,8 +96,23 @@ public abstract class BaseJsonValidator extends ValidationMessageHandler impleme
         return Math.abs(n1 - n2) < 1e-12;
     }
 
-    protected static void debug(Logger logger, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        logger.debug("validate( {}, {}, {})", node, rootNode, instanceLocation);
+    public static void debug(Logger logger, ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        //logger.debug("validate( {}, {}, {})", node, rootNode, instanceLocation);
+        // The below is equivalent to the above but as there are more than 2 arguments
+        // the var-arg method is used and an array needs to be allocated even if debug
+        // is not enabled
+        if (executionContext.getExecutionConfig().isDebugEnabled() && logger.isDebugEnabled()) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("validate( ");
+            builder.append(node.toString());
+            builder.append(", ");
+            builder.append(rootNode.toString());
+            builder.append(", ");
+            builder.append(instanceLocation.toString());
+            builder.append(")");
+            logger.debug(builder.toString());
+        }
     }
 
     /**
diff --git a/src/main/java/com/networknt/schema/CachedSupplier.java b/src/main/java/com/networknt/schema/CachedSupplier.java
index 109f36c..4411bdd 100644
--- a/src/main/java/com/networknt/schema/CachedSupplier.java
+++ b/src/main/java/com/networknt/schema/CachedSupplier.java
@@ -23,8 +23,8 @@ import java.util.function.Supplier;
  * @param <T> the type cached
  */
 public class CachedSupplier<T> implements Supplier<T> {
-    private final Supplier<T> delegate;
-    private T cache = null;
+    private volatile Supplier<T> delegate;
+    private volatile T cache = null;
 
     public CachedSupplier(Supplier<T> delegate) {
         this.delegate = delegate;
@@ -32,10 +32,16 @@ public class CachedSupplier<T> implements Supplier<T> {
 
     @Override
     public T get() {
-        if (cache == null) {
-            cache = delegate.get();
+        if (this.delegate == null) {
+            return this.cache;
         }
-        return cache;
-    }
 
+        synchronized(this) {
+            if (this.delegate != null) {
+                this.cache = this.delegate.get();
+                this.delegate = null;
+            }
+        }
+        return this.cache;
+    }
 }
diff --git a/src/main/java/com/networknt/schema/ConstValidator.java b/src/main/java/com/networknt/schema/ConstValidator.java
index 291144b..bcabec5 100644
--- a/src/main/java/com/networknt/schema/ConstValidator.java
+++ b/src/main/java/com/networknt/schema/ConstValidator.java
@@ -34,7 +34,7 @@ public class ConstValidator extends BaseJsonValidator implements JsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (schemaNode.isNumber() && node.isNumber()) {
             if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) {
@@ -45,7 +45,7 @@ public class ConstValidator extends BaseJsonValidator implements JsonValidator {
             }
         } else if (!schemaNode.equals(node)) {
             return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
-                    .locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText()).build());
+                    .locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText(), node.asText()).build());
         }
         return Collections.emptySet();
     }
diff --git a/src/main/java/com/networknt/schema/ContainsValidator.java b/src/main/java/com/networknt/schema/ContainsValidator.java
index 1ef0199..adcdcde 100644
--- a/src/main/java/com/networknt/schema/ContainsValidator.java
+++ b/src/main/java/com/networknt/schema/ContainsValidator.java
@@ -72,7 +72,7 @@ public class ContainsValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         // ignores non-arrays
         Set<ValidationMessage> results = null;
diff --git a/src/main/java/com/networknt/schema/ContentEncodingValidator.java b/src/main/java/com/networknt/schema/ContentEncodingValidator.java
index 11e79ea..6f2bbbb 100644
--- a/src/main/java/com/networknt/schema/ContentEncodingValidator.java
+++ b/src/main/java/com/networknt/schema/ContentEncodingValidator.java
@@ -66,7 +66,7 @@ public class ContentEncodingValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         // Ignore non-strings
         JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
diff --git a/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java b/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java
index f4dd194..28e435b 100644
--- a/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java
+++ b/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java
@@ -90,7 +90,7 @@ public class ContentMediaTypeValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         // Ignore non-strings
         JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
diff --git a/src/main/java/com/networknt/schema/DependenciesValidator.java b/src/main/java/com/networknt/schema/DependenciesValidator.java
index 596e77a..72b6c78 100644
--- a/src/main/java/com/networknt/schema/DependenciesValidator.java
+++ b/src/main/java/com/networknt/schema/DependenciesValidator.java
@@ -63,7 +63,7 @@ public class DependenciesValidator extends BaseJsonValidator implements JsonVali
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = null;
 
diff --git a/src/main/java/com/networknt/schema/DependentRequired.java b/src/main/java/com/networknt/schema/DependentRequired.java
index 8350726..8c7110f 100644
--- a/src/main/java/com/networknt/schema/DependentRequired.java
+++ b/src/main/java/com/networknt/schema/DependentRequired.java
@@ -47,7 +47,7 @@ public class DependentRequired extends BaseJsonValidator implements JsonValidato
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
 
diff --git a/src/main/java/com/networknt/schema/DependentSchemas.java b/src/main/java/com/networknt/schema/DependentSchemas.java
index 253ed04..589a8e5 100644
--- a/src/main/java/com/networknt/schema/DependentSchemas.java
+++ b/src/main/java/com/networknt/schema/DependentSchemas.java
@@ -44,15 +44,23 @@ public class DependentSchemas extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = null;
         for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
             String pname = it.next();
             JsonSchema schema = this.schemaDependencies.get(pname);
             if (schema != null) {
-                Set<ValidationMessage> schemaDependenciesErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+                Set<ValidationMessage> schemaDependenciesErrors = !walk
+                        ? schema.validate(executionContext, node, rootNode, instanceLocation)
+                        : schema.walk(executionContext, node, rootNode, instanceLocation, true);
                 if (!schemaDependenciesErrors.isEmpty()) {
                     if (errors == null) {
                         errors = new LinkedHashSet<>();
@@ -72,7 +80,7 @@ public class DependentSchemas extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemaDependencies.values()) {
             schema.walk(executionContext, node, rootNode, instanceLocation, false);
diff --git a/src/main/java/com/networknt/schema/DynamicRefValidator.java b/src/main/java/com/networknt/schema/DynamicRefValidator.java
index ea4da48..10ce601 100644
--- a/src/main/java/com/networknt/schema/DynamicRefValidator.java
+++ b/src/main/java/com/networknt/schema/DynamicRefValidator.java
@@ -93,7 +93,7 @@ public class DynamicRefValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         JsonSchema refSchema = this.schema.getSchema();
         if (refSchema == null) {
             ValidationMessage validationMessage = message().type(ValidatorTypeCode.DYNAMIC_REF.getValue())
@@ -107,7 +107,7 @@ public class DynamicRefValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances,
         // these schemas will be cached along with config. We have to replace the config for cached $ref references
         // with the latest config. Reset the config.
diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java
index 582666d..7c77241 100644
--- a/src/main/java/com/networknt/schema/EnumValidator.java
+++ b/src/main/java/com/networknt/schema/EnumValidator.java
@@ -82,7 +82,7 @@ public class EnumValidator extends BaseJsonValidator implements JsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (node.isNumber()) {
             node = processNumberNode(node);
diff --git a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java
index 35db8dd..0460481 100644
--- a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java
+++ b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java
@@ -98,7 +98,7 @@ public class ExclusiveMaximumValidator extends BaseJsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!JsonNodeUtil.isNumber(node, validationContext.getConfig())) {
             // maximum only applies to numbers
diff --git a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java
index ab2568f..aea9c36 100644
--- a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java
+++ b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java
@@ -105,7 +105,7 @@ public class ExclusiveMinimumValidator extends BaseJsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
             // minimum only applies to numbers
diff --git a/src/main/java/com/networknt/schema/ExecutionConfig.java b/src/main/java/com/networknt/schema/ExecutionConfig.java
index 516241d..e4f83ee 100644
--- a/src/main/java/com/networknt/schema/ExecutionConfig.java
+++ b/src/main/java/com/networknt/schema/ExecutionConfig.java
@@ -54,7 +54,16 @@ public class ExecutionConfig {
      * Determine if the validation execution can fail fast.
      */
     private boolean failFast = false;
-    
+
+    /**
+     * Determine if debugging features such that logging are switched on.
+     * <p>
+     * This is turned off by default. This is present because the library attempts
+     * to log debug logs at each validation node and the logger evaluation on
+     * whether the logger is turned on is impacting performance.
+     */
+    private boolean debugEnabled = false;
+
     /**
      * Gets the locale to use for formatting messages.
      * 
@@ -180,4 +189,21 @@ public class ExecutionConfig {
                 "annotationCollectionFilter must not be null");
     }
 
+    /**
+     * Gets if debugging features such as logging is switched on.
+     *
+     * @return true if debug is enabled
+     */
+    public boolean isDebugEnabled() {
+        return debugEnabled;
+    }
+
+    /**
+     * Sets if debugging features such as logging is switched on.
+     *
+     * @param debugEnabled true to enable debug
+     */
+    public void setDebugEnabled(boolean debugEnabled) {
+        this.debugEnabled = debugEnabled;
+    }
 }
diff --git a/src/main/java/com/networknt/schema/ExecutionContext.java b/src/main/java/com/networknt/schema/ExecutionContext.java
index 8ca79cd..f2ca8e1 100644
--- a/src/main/java/com/networknt/schema/ExecutionContext.java
+++ b/src/main/java/com/networknt/schema/ExecutionContext.java
@@ -27,7 +27,6 @@ import java.util.Stack;
 public class ExecutionContext {
     private ExecutionConfig executionConfig;
     private CollectorContext collectorContext = null;
-    private ValidatorState validatorState = null;
     private Stack<DiscriminatorContext> discriminatorContexts = null;
     private JsonNodeAnnotations annotations = null;
     private JsonNodeResults results = null;
@@ -153,24 +152,6 @@ public class ExecutionContext {
         this.failFast = failFast;
     }
 
-    /**
-     * Gets the validator state.
-     * 
-     * @return the validator state
-     */
-    public ValidatorState getValidatorState() {
-        return validatorState;
-    }
-
-    /**
-     * Sets the validator state.
-     * 
-     * @param validatorState the validator state
-     */
-    public void setValidatorState(ValidatorState validatorState) {
-        this.validatorState = validatorState;
-    }
-
     public DiscriminatorContext getCurrentDiscriminatorContext() {
         if (this.discriminatorContexts == null) {
             return null;
diff --git a/src/main/java/com/networknt/schema/FalseValidator.java b/src/main/java/com/networknt/schema/FalseValidator.java
index 8018a76..8032e3e 100644
--- a/src/main/java/com/networknt/schema/FalseValidator.java
+++ b/src/main/java/com/networknt/schema/FalseValidator.java
@@ -36,7 +36,7 @@ public class FalseValidator extends BaseJsonValidator implements JsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // For the false validator, it is always not valid
         return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
                 .locale(executionContext.getExecutionConfig().getLocale())
diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java
index 9665762..7177f3f 100644
--- a/src/main/java/com/networknt/schema/FormatValidator.java
+++ b/src/main/java/com/networknt/schema/FormatValidator.java
@@ -59,7 +59,7 @@ public class FormatValidator extends BaseFormatJsonValidator implements JsonVali
     }
     
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         /*
          * Annotations must be collected even if the format is unknown according to the specification.
          */
diff --git a/src/main/java/com/networknt/schema/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java
index efb49ed..13ed4e8 100644
--- a/src/main/java/com/networknt/schema/IfValidator.java
+++ b/src/main/java/com/networknt/schema/IfValidator.java
@@ -65,7 +65,7 @@ public class IfValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         boolean ifConditionPassed = false;
 
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
index 7912ebd..4407f91 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -87,7 +87,7 @@ public class ItemsValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!node.isArray() && !this.validationContext.getConfig().isTypeLoose()) {
             // ignores non-arrays
diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java
index 9edcb37..d8efd1a 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator202012.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java
@@ -65,7 +65,7 @@ public class ItemsValidator202012 extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         // ignores non-arrays
         if (node.isArray()) {
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index d766123..7257681 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -58,7 +58,6 @@ public class JsonSchema extends BaseJsonValidator {
     private boolean validatorsLoaded = false;
     private boolean recursiveAnchor = false;
 
-    private JsonValidator requiredValidator = null;
     private TypeValidator typeValidator;
 
     private final String id;
@@ -161,7 +160,6 @@ public class JsonSchema extends BaseJsonValidator {
         this.validators = copy.validators;
         this.validatorsLoaded = copy.validatorsLoaded;
         this.recursiveAnchor = copy.recursiveAnchor;
-        this.requiredValidator = copy.requiredValidator;
         this.typeValidator = copy.typeValidator;
         this.id = copy.id;
     }
@@ -189,7 +187,6 @@ public class JsonSchema extends BaseJsonValidator {
         copy.evaluationParentSchema = refEvaluationParentSchema;
         // Validator state is reset due to the changes in evaluation path
         copy.validatorsLoaded = false;
-        copy.requiredValidator = null;
         copy.typeValidator = null;
         copy.validators = null;
         return copy;
@@ -204,7 +201,6 @@ public class JsonSchema extends BaseJsonValidator {
                     copy.getValidationContext().getSchemaResources(),
                     copy.getValidationContext().getDynamicAnchors());
             copy.validatorsLoaded = false;
-            copy.requiredValidator = null;
             copy.typeValidator = null;
             copy.validators = null;
             return copy;
@@ -472,8 +468,6 @@ public class JsonSchema extends BaseJsonValidator {
 
                     if ("$ref".equals(pname)) {
                         refValidator = validator;
-                    } else if ("required".equals(pname)) {
-                        this.requiredValidator = validator;
                     } else if ("type".equals(pname)) {
                         if (validator instanceof TypeValidator) {
                             this.typeValidator = (TypeValidator) validator;
@@ -537,9 +531,6 @@ public class JsonSchema extends BaseJsonValidator {
         }
 
         SetView<ValidationMessage> errors = null;
-        // Set the walkEnabled and isValidationEnabled flag in internal validator state.
-        setValidatorState(executionContext, false, true);
-
         for (JsonValidator v : getValidators()) {
             Set<ValidationMessage> results = null;
 
@@ -894,8 +885,6 @@ public class JsonSchema extends BaseJsonValidator {
      * @return ValidationResult
      */
     private ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
-        // Set the walkEnabled and isValidationEnabled flag in internal validator state.
-        setValidatorState(executionContext, false, true);
         // Validate.
         Set<ValidationMessage> errors = validate(executionContext, jsonNode, rootNode, instanceLocation);
 
@@ -990,8 +979,6 @@ public class JsonSchema extends BaseJsonValidator {
 
     private ValidationResult walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation, boolean shouldValidateSchema) {
-        // Set the walkEnabled flag in internal validator state.
-        setValidatorState(executionContext, true, shouldValidateSchema);
         // Walk through the schema.
         Set<ValidationMessage> errors = walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
 
@@ -1041,30 +1028,11 @@ public class JsonSchema extends BaseJsonValidator {
     }
 
     /************************ END OF WALK METHODS **********************************/
-
-    private static void setValidatorState(ExecutionContext executionContext, boolean isWalkEnabled,
-            boolean shouldValidateSchema) {
-        // Get the Validator state object storing validation data
-        ValidatorState validatorState = executionContext.getValidatorState();
-        if (validatorState == null) {
-            // If one has not been created, instantiate one
-            executionContext.setValidatorState(new ValidatorState(isWalkEnabled, shouldValidateSchema));
-        }
-    }
-
     @Override
     public String toString() {
         return "\"" + getEvaluationPath() + "\" : " + getSchemaNode().toString();
     }
 
-    public boolean hasRequiredValidator() {
-        return this.requiredValidator != null;
-    }
-
-    public JsonValidator getRequiredValidator() {
-        return this.requiredValidator;
-    }
-
     public boolean hasTypeValidator() {
         return getTypeValidator() != null;
     }
diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java
index b8ee041..57b0c02 100644
--- a/src/main/java/com/networknt/schema/MaxItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MaxItemsValidator.java
@@ -40,7 +40,7 @@ public class MaxItemsValidator extends BaseJsonValidator implements JsonValidato
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (node.isArray()) {
             if (node.size() > max) {
diff --git a/src/main/java/com/networknt/schema/MaxLengthValidator.java b/src/main/java/com/networknt/schema/MaxLengthValidator.java
index 08882ed..e304478 100644
--- a/src/main/java/com/networknt/schema/MaxLengthValidator.java
+++ b/src/main/java/com/networknt/schema/MaxLengthValidator.java
@@ -40,7 +40,7 @@ public class MaxLengthValidator extends BaseJsonValidator implements JsonValidat
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
         if (nodeType != JsonType.STRING) {
diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
index 0d33b12..f6e5872 100644
--- a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
@@ -42,7 +42,7 @@ public class MaxPropertiesValidator extends BaseJsonValidator implements JsonVal
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (node.isObject()) {
             if (node.size() > max) {
diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java
index 03a7c9c..261fa47 100644
--- a/src/main/java/com/networknt/schema/MaximumValidator.java
+++ b/src/main/java/com/networknt/schema/MaximumValidator.java
@@ -108,7 +108,7 @@ public class MaximumValidator extends BaseJsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
             // maximum only applies to numbers
diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java
index 83db6ec..52f927b 100644
--- a/src/main/java/com/networknt/schema/MinItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MinItemsValidator.java
@@ -39,7 +39,7 @@ public class MinItemsValidator extends BaseJsonValidator implements JsonValidato
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (node.isArray()) {
             if (node.size() < min) {
diff --git a/src/main/java/com/networknt/schema/MinLengthValidator.java b/src/main/java/com/networknt/schema/MinLengthValidator.java
index c82b8ec..1161ba2 100644
--- a/src/main/java/com/networknt/schema/MinLengthValidator.java
+++ b/src/main/java/com/networknt/schema/MinLengthValidator.java
@@ -40,7 +40,7 @@ public class MinLengthValidator extends BaseJsonValidator implements JsonValidat
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
         if (nodeType != JsonType.STRING) {
diff --git a/src/main/java/com/networknt/schema/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
index 1ff5664..2867a08 100644
--- a/src/main/java/com/networknt/schema/MinPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
@@ -42,7 +42,7 @@ public class MinPropertiesValidator extends BaseJsonValidator implements JsonVal
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (node.isObject()) {
             if (node.size() < min) {
diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java
index 8b2e5e7..3f35948 100644
--- a/src/main/java/com/networknt/schema/MinimumValidator.java
+++ b/src/main/java/com/networknt/schema/MinimumValidator.java
@@ -115,7 +115,7 @@ public class MinimumValidator extends BaseJsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
             // minimum only applies to numbers
diff --git a/src/main/java/com/networknt/schema/MultipleOfValidator.java b/src/main/java/com/networknt/schema/MultipleOfValidator.java
index 95b57ce..bcd29d2 100644
--- a/src/main/java/com/networknt/schema/MultipleOfValidator.java
+++ b/src/main/java/com/networknt/schema/MultipleOfValidator.java
@@ -42,7 +42,7 @@ public class MultipleOfValidator extends BaseJsonValidator implements JsonValida
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         if (this.divisor != null) {
             BigDecimal dividend = getDividend(node);
             if (dividend != null) {
diff --git a/src/main/java/com/networknt/schema/NotAllowedValidator.java b/src/main/java/com/networknt/schema/NotAllowedValidator.java
index 3d9952b..401729d 100644
--- a/src/main/java/com/networknt/schema/NotAllowedValidator.java
+++ b/src/main/java/com/networknt/schema/NotAllowedValidator.java
@@ -41,7 +41,7 @@ public class NotAllowedValidator extends BaseJsonValidator implements JsonValida
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = null;
 
diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java
index a2d7f8a..e943a51 100644
--- a/src/main/java/com/networknt/schema/NotValidator.java
+++ b/src/main/java/com/networknt/schema/NotValidator.java
@@ -37,15 +37,22 @@ public class NotValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         Set<ValidationMessage> errors = null;
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         // Save flag as nested schema evaluation shouldn't trigger fail fast
         boolean failFast = executionContext.isFailFast();
         try {
             executionContext.setFailFast(false);
-            errors = this.schema.validate(executionContext, node, rootNode, instanceLocation);
+            errors = !walk ? this.schema.validate(executionContext, node, rootNode, instanceLocation)
+                    : this.schema.walk(executionContext, node, rootNode, instanceLocation, true);
         } finally {
             // Restore flag
             executionContext.setFailFast(failFast);
@@ -62,7 +69,7 @@ public class NotValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
 
         Set<ValidationMessage> errors = this.schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
index 1a49d98..c78df98 100644
--- a/src/main/java/com/networknt/schema/OneOfValidator.java
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -52,16 +52,16 @@ public class OneOfValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        Set<ValidationMessage> errors = null;
-
-        debug(logger, node, rootNode, instanceLocation);
-
-        ValidatorState state = executionContext.getValidatorState();
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        // this is a complex validator, we set the flag to true
-        state.setComplexValidator(true);
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        Set<ValidationMessage> errors = null;
 
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         int numberOfValidSchema = 0;
         int index = 0;
         SetView<ValidationMessage> childErrors = null;
@@ -82,24 +82,15 @@ public class OneOfValidator extends BaseJsonValidator {
             executionContext.setFailFast(false);
             for (JsonSchema schema : this.schemas) {
                 Set<ValidationMessage> schemaErrors = Collections.emptySet();
-
-                // Reset state in case the previous validator did not match
-                state.setMatchedNode(true);
-
-                if (!state.isWalkEnabled()) {
+                if (!walk) {
                     schemaErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
                 } else {
                     schemaErrors = schema.walk(executionContext, node, rootNode, instanceLocation,
-                            state.isValidationEnabled());
+                            true);
                 }
 
                 // check if any validation errors have occurred
                 if (schemaErrors.isEmpty()) {
-                    // check whether there are no errors HOWEVER we have validated the exact
-                    // validator
-                    if (!state.hasMatchedNode()) {
-                        continue;
-                    }
                     numberOfValidSchema++;
                     if (indexes == null) {
                         indexes = new ArrayList<>();
@@ -194,15 +185,6 @@ public class OneOfValidator extends BaseJsonValidator {
                 errors = Collections.singleton(message);
             }
         }
-
-        // Make sure to signal parent handlers we matched
-        if (errors == null || errors.isEmpty()) {
-            state.setMatchedNode(true);
-        }
-
-        // reset the ValidatorState object
-        resetValidatorState(executionContext);
-
         return errors != null ? errors : Collections.emptySet();
     }
 
@@ -232,17 +214,11 @@ public class OneOfValidator extends BaseJsonValidator {
         return this.canShortCircuit;
     }
 
-    private static void resetValidatorState(ExecutionContext executionContext) {
-        ValidatorState state = executionContext.getValidatorState();
-        state.setComplexValidator(false);
-        state.setMatchedNode(true);
-    }
-
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         HashSet<ValidationMessage> validationMessages = new LinkedHashSet<>();
         if (shouldValidateSchema) {
-            validationMessages.addAll(validate(executionContext, node, rootNode, instanceLocation));
+            validationMessages.addAll(validate(executionContext, node, rootNode, instanceLocation, true));
         } else {
             for (JsonSchema schema : this.schemas) {
                 schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
diff --git a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
index 797558a..94cb16c 100644
--- a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
@@ -50,7 +50,7 @@ public class PatternPropertiesValidator extends BaseJsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!node.isObject()) {
             return Collections.emptySet();
diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java
index db6f32e..5605b8f 100644
--- a/src/main/java/com/networknt/schema/PatternValidator.java
+++ b/src/main/java/com/networknt/schema/PatternValidator.java
@@ -49,7 +49,7 @@ public class PatternValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
         if (nodeType != JsonType.STRING) {
diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
index ccda858..a1db79a 100644
--- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java
+++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
@@ -60,7 +60,7 @@ public class PrefixItemsValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // ignores non-arrays
         if (node.isArray()) {
             SetView<ValidationMessage> errors = null;
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
index f378905..36bcab8 100644
--- a/src/main/java/com/networknt/schema/PropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -56,15 +56,17 @@ public class PropertiesValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        SetView<ValidationMessage> errors = null;
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
+        SetView<ValidationMessage> errors = null;
 
-        Set<ValidationMessage> requiredErrors = null;
         Set<String> matchedInstancePropertyNames = null;
         boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext);
         for (Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
@@ -77,15 +79,7 @@ public class PropertiesValidator extends BaseJsonValidator {
                     }
                     matchedInstancePropertyNames.add(entry.getKey());
                 }
-                // check whether this is a complex validator. save the state
-                boolean isComplex = state.isComplexValidator();
-               // if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated
-                if (isComplex) {
-                    state.setMatchedNode(true);
-                    // reset the complex validator for child element validation, and reset it after the return from the recursive call
-                    state.setComplexValidator(false);
-                }
-                if (!state.isWalkEnabled()) {
+                if (!walk) {
                     //validate the child element(s)
                     Set<ValidationMessage> result = entry.getValue().validate(executionContext, propertyNode, rootNode,
                             path);
@@ -100,17 +94,10 @@ public class PropertiesValidator extends BaseJsonValidator {
                     if (errors == null) {
                         errors = new SetView<>();
                     }
-                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
-                }
-
-                // reset the complex flag to the original value before the recursive call
-                if (isComplex) {
-                    state.setComplexValidator(isComplex);
-                    // if this was a complex validator, the node has matched and has been validated
-                    state.setMatchedNode(true);
+                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
                 }
             } else {
-                if (state.isWalkEnabled()) {
+                if (walk) {
                     // This tries to make the walk listener consistent between when validation is
                     // enabled or disabled as when validation is disabled it will walk where node is
                     // null.
@@ -119,30 +106,7 @@ public class PropertiesValidator extends BaseJsonValidator {
                     if (errors == null) {
                         errors = new SetView<>();
                     }
-                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
-                }
-
-                // check whether the node which has not matched was mandatory or not
-
-                // the node was mandatory, decide which behavior to employ when validator has
-                // not matched
-                if (state.isComplexValidator()) {
-                    if (getParentSchema().hasRequiredValidator()) {
-
-                        // The required validator runs for all properties in the node and not just the
-                        // current propertyNode
-                        if (requiredErrors == null) {
-                            // Note that the results of the required validator shouldn't be added to the errors here, the required validator
-                            // will still trigger normally in the schema
-                            requiredErrors = getParentSchema().getRequiredValidator().validate(executionContext, node,
-                                    rootNode, instanceLocation);
-                        }
-                        if (!requiredErrors.isEmpty()) {
-                            // this was a complex validator (ex oneOf) and the node has not been matched
-                            state.setMatchedNode(false);
-                            return Collections.emptySet();
-                        }
-                    }
+                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
                 }
             }
         }
@@ -167,7 +131,7 @@ public class PropertiesValidator extends BaseJsonValidator {
         }
         if (shouldValidateSchema) {
             validationMessages.union(validate(executionContext, node == null ? MissingNode.getInstance() : node, rootNode,
-                    instanceLocation));
+                    instanceLocation, true));
         } else {
             WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner();
             for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
diff --git a/src/main/java/com/networknt/schema/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/PropertyNamesValidator.java
index 6edc145..3d895f0 100644
--- a/src/main/java/com/networknt/schema/PropertyNamesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertyNamesValidator.java
@@ -35,7 +35,7 @@ public class PropertyNamesValidator extends BaseJsonValidator implements JsonVal
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = null;
         for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
diff --git a/src/main/java/com/networknt/schema/ReadOnlyValidator.java b/src/main/java/com/networknt/schema/ReadOnlyValidator.java
index 9ea33ea..c7b0b9a 100644
--- a/src/main/java/com/networknt/schema/ReadOnlyValidator.java
+++ b/src/main/java/com/networknt/schema/ReadOnlyValidator.java
@@ -41,7 +41,7 @@ public class ReadOnlyValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         if (this.readOnly) {
             return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
                     .locale(executionContext.getExecutionConfig().getLocale())
diff --git a/src/main/java/com/networknt/schema/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
index 73a72ce..ad8f009 100644
--- a/src/main/java/com/networknt/schema/RecursiveRefValidator.java
+++ b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
@@ -90,7 +90,7 @@ public class RecursiveRefValidator extends BaseJsonValidator {
     
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         JsonSchema refSchema = this.schema.getSchema();
         if (refSchema == null) {
             ValidationMessage validationMessage = message().type(ValidatorTypeCode.RECURSIVE_REF.getValue())
@@ -104,7 +104,7 @@ public class RecursiveRefValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances,
         // these schemas will be cached along with config. We have to replace the config for cached $ref references
         // with the latest config. Reset the config.
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
index f7df836..823e43c 100644
--- a/src/main/java/com/networknt/schema/RefValidator.java
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -177,7 +177,7 @@ public class RefValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         JsonSchema refSchema = this.schema.getSchema();
         if (refSchema == null) {
             ValidationMessage validationMessage = message().type(ValidatorTypeCode.REF.getValue())
@@ -191,7 +191,7 @@ public class RefValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances,
         // these schemas will be cached along with config. We have to replace the config for cached $ref references
         // with the latest config. Reset the config.
diff --git a/src/main/java/com/networknt/schema/RequiredValidator.java b/src/main/java/com/networknt/schema/RequiredValidator.java
index 1b6b816..1e084c1 100644
--- a/src/main/java/com/networknt/schema/RequiredValidator.java
+++ b/src/main/java/com/networknt/schema/RequiredValidator.java
@@ -40,7 +40,7 @@ public class RequiredValidator extends BaseJsonValidator implements JsonValidato
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (!node.isObject()) {
             return Collections.emptySet();
diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
index 1240da3..fbc2247 100644
--- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -19,6 +19,9 @@ package com.networknt.schema;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.networknt.schema.i18n.DefaultMessageSource;
 import com.networknt.schema.i18n.MessageSource;
+import com.networknt.schema.regex.ECMAScriptRegularExpressionFactory;
+import com.networknt.schema.regex.JDKRegularExpressionFactory;
+import com.networknt.schema.regex.RegularExpressionFactory;
 import com.networknt.schema.walk.DefaultItemWalkListenerRunner;
 import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
 import com.networknt.schema.walk.DefaultPropertyWalkListenerRunner;
@@ -59,9 +62,9 @@ public class SchemaValidatorsConfig {
     private ApplyDefaultsStrategy applyDefaultsStrategy = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
 
     /**
-     * When set to true, use ECMA-262 compatible validator
+     * Used to create {@link com.networknt.schema.regex.RegularExpression}.
      */
-    private boolean ecma262Validator;
+    private RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance();
 
     /**
      * When set to true, use Java-specific semantics rather than native JavaScript
@@ -268,12 +271,55 @@ public class SchemaValidatorsConfig {
         this.handleNullableField = handleNullableField;
     }
 
+    /**
+     * Gets whether to use a ECMA-262 compliant regular expression validator.
+     * <p>
+     * This defaults to the false and setting true require inclusion of optional
+     * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+     *
+     * @return true if ECMA-262 compliant
+     */
     public boolean isEcma262Validator() {
-        return this.ecma262Validator;
+        return !(this.regularExpressionFactory instanceof JDKRegularExpressionFactory);
     }
 
+    /**
+     * Sets whether to use a ECMA-262 compliant regular expression validator.
+     * <p>
+     * This defaults to the false and setting true require inclusion of optional
+     * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+     *
+     * @param ecma262Validator true if ECMA-262 compliant
+     */
     public void setEcma262Validator(boolean ecma262Validator) {
-        this.ecma262Validator = ecma262Validator;
+        this.regularExpressionFactory = ecma262Validator ? ECMAScriptRegularExpressionFactory.getInstance()
+                : JDKRegularExpressionFactory.getInstance();
+    }
+
+    /**
+     * Gets the regular expression factory.
+     * <p>
+     * This defaults to the JDKRegularExpressionFactory and the implementations
+     * require inclusion of optional org.jruby.joni:joni or org.graalvm.js:js dependencies.
+     *
+     * @return the factory
+     */
+    public RegularExpressionFactory getRegularExpressionFactory() {
+        return regularExpressionFactory;
+    }
+
+    /**
+     * Sets the regular expression factory.
+     * <p>
+     * This defaults to the JDKRegularExpressionFactory and the implementations
+     * require inclusion of optional org.jruby.joni:joni or org.graalvm.js:js dependencies.
+     *
+     * @see JDKRegularExpressionFactory
+     * @see ECMAScriptRegularExpressionFactory
+     * @param regularExpressionFactory the factory
+     */
+    public void setRegularExpressionFactory(RegularExpressionFactory regularExpressionFactory) {
+        this.regularExpressionFactory = regularExpressionFactory;
     }
 
     public boolean isJavaSemantics() {
diff --git a/src/main/java/com/networknt/schema/TrueValidator.java b/src/main/java/com/networknt/schema/TrueValidator.java
index 0c003d9..380ed5d 100644
--- a/src/main/java/com/networknt/schema/TrueValidator.java
+++ b/src/main/java/com/networknt/schema/TrueValidator.java
@@ -33,7 +33,7 @@ public class TrueValidator extends BaseJsonValidator implements JsonValidator {
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // For the true validator, it is always valid which means there is no ValidationMessage.
         return Collections.emptySet();
     }
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index 245f690..2cadb1b 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -52,7 +52,7 @@ public class TypeValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (this.schemaType == JsonType.UNION) {
             return this.unionTypeValidator.validate(executionContext, node, rootNode, instanceLocation);
diff --git a/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
index a01273f..49419f2 100644
--- a/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
+++ b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
@@ -59,7 +59,7 @@ public class UnevaluatedItemsValidator extends BaseJsonValidator {
             return Collections.emptySet();
         }
 
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         /*
          * Keywords renamed in 2020-12
          * 
diff --git a/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java
index 61b6ced..e40b242 100644
--- a/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java
@@ -50,7 +50,7 @@ public class UnevaluatedPropertiesValidator extends BaseJsonValidator {
             return Collections.emptySet();
         }
 
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         // Get all the valid adjacent annotations
         Predicate<JsonNodeAnnotation> validEvaluationPathFilter = a -> {
             return executionContext.getResults().isValid(instanceLocation, a.getEvaluationPath());
diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java
index c051ea3..184a119 100644
--- a/src/main/java/com/networknt/schema/UnionTypeValidator.java
+++ b/src/main/java/com/networknt/schema/UnionTypeValidator.java
@@ -65,7 +65,7 @@ public class UnionTypeValidator extends BaseJsonValidator implements JsonValidat
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig());
 
diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
index a061b97..2740d40 100644
--- a/src/main/java/com/networknt/schema/UniqueItemsValidator.java
+++ b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
@@ -42,7 +42,7 @@ public class UniqueItemsValidator extends BaseJsonValidator implements JsonValid
     }
 
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
 
         if (unique) {
             Set<JsonNode> set = new HashSet<JsonNode>();
diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java
deleted file mode 100644
index b522bd8..0000000
--- a/src/main/java/com/networknt/schema/ValidatorState.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2020 Network New Technologies Inc.
- *
- * 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 com.networknt.schema;
-
-public class ValidatorState {
-    /**
-     * Flag set when a node has matched Works in conjunction with the next flag:
-     * isComplexValidator, to be used for complex validators such as oneOf, for ex
-     */
-    private boolean matchedNode = true;
-
-    /**
-     * Flag set if complex validators such as oneOf, for ex, neeed to have their
-     * properties validated. The PropertiesValidator is not aware generally of a
-     * complex validator is being validated or a simple poperty tree
-     */
-    private boolean isComplexValidator = false;
-
-    /**
-     * Flag to check if walking is enabled.
-     */
-    private boolean isWalkEnabled = false;
-
-    /**
-     * Flag to check if validation is enabled while walking.
-     */
-    private boolean isValidationEnabled = false;
-
-    /**
-     * Constructor for validation state.
-     */
-    public ValidatorState() {
-    }
-
-    /**
-     * Constructor for validation state.
-     * 
-     * @param walkEnabled       whether walk is enabled
-     * @param validationEnabled whether validation is enabled
-     */
-    public ValidatorState(boolean walkEnabled, boolean validationEnabled) {
-        this.isWalkEnabled = walkEnabled;
-        this.isValidationEnabled = validationEnabled;
-    }
-
-    public void setMatchedNode(boolean matchedNode) {
-        this.matchedNode = matchedNode;
-    }
-
-    public boolean hasMatchedNode() {
-        return matchedNode;
-    }
-
-    public boolean isComplexValidator() {
-        return isComplexValidator;
-    }
-
-    public void setComplexValidator(boolean isComplexValidator) {
-        this.isComplexValidator = isComplexValidator;
-    }
-
-    public boolean isWalkEnabled() {
-        return isWalkEnabled;
-    }
-
-    public void setWalkEnabled(boolean isWalkEnabled) {
-        this.isWalkEnabled = isWalkEnabled;
-    }
-
-    public boolean isValidationEnabled() {
-        return isValidationEnabled;
-    }
-
-    public void setValidationEnabled(boolean isValidationEnabled) {
-        this.isValidationEnabled = isValidationEnabled;
-    }
-
-}
diff --git a/src/main/java/com/networknt/schema/WriteOnlyValidator.java b/src/main/java/com/networknt/schema/WriteOnlyValidator.java
index e13da5a..c441e6b 100644
--- a/src/main/java/com/networknt/schema/WriteOnlyValidator.java
+++ b/src/main/java/com/networknt/schema/WriteOnlyValidator.java
@@ -25,7 +25,7 @@ public class WriteOnlyValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        debug(logger, executionContext, node, rootNode, instanceLocation);
         if (this.writeOnly) {
             return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
                     .locale(executionContext.getExecutionConfig().getLocale())
diff --git a/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java
new file mode 100644
index 0000000..72c5fec
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import com.networknt.schema.utils.Classes;
+
+/**
+ * ECMAScript regular expression factory that chooses between GraalJS or Joni
+ * implementations depending on which is on the classpath.
+ */
+public class ECMAScriptRegularExpressionFactory implements RegularExpressionFactory {
+    private static final boolean JONI_PRESENT = Classes.isPresent("org.joni.Regex",
+            ECMAScriptRegularExpressionFactory.class.getClassLoader());
+    private static final boolean GRAALJS_PRESENT = Classes.isPresent("com.oracle.truffle.js.parser.GraalJSEvaluator",
+            ECMAScriptRegularExpressionFactory.class.getClassLoader());
+
+    private static final RegularExpressionFactory DELEGATE = GRAALJS_PRESENT
+            ? GraalJSRegularExpressionFactory.getInstance()
+            : JoniRegularExpressionFactory.getInstance();
+    
+    public static final ECMAScriptRegularExpressionFactory INSTANCE = new ECMAScriptRegularExpressionFactory();
+
+    public static ECMAScriptRegularExpressionFactory getInstance() {
+        if (!JONI_PRESENT && !GRAALJS_PRESENT) {
+            throw new IllegalArgumentException(
+                    "Either org.jruby.joni:joni or org.graalvm.js:js needs to be present in the classpath");
+        }
+        return INSTANCE;
+    }
+
+    @Override
+    public RegularExpression getRegularExpression(String regex) {
+        return DELEGATE.getRegularExpression(regex);
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java b/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java
new file mode 100644
index 0000000..7e41669
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import org.graalvm.polyglot.Context;
+
+/**
+ * Factory for the js {@link Context}.
+ */
+public class GraalJSContextFactory {
+    /**
+     * The holder defers the classloading until it is used.
+     */
+    private static class Holder {
+        private static final Context INSTANCE = Context.newBuilder("js").option("engine.WarnInterpreterOnly", "false")
+                .build();
+    }
+
+    /**
+     * Gets the singleton instance of the Context.
+     * <p>
+     * This may need to be closed to release resources if no longer needed.
+     *
+     * @return the Context
+     */
+    public static Context getInstance() {
+        return Holder.INSTANCE;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java
new file mode 100644
index 0000000..0ab33cd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import org.graalvm.polyglot.Value;
+
+/**
+ * GraalJS {@link RegularExpression}.
+ * <p>
+ * This requires a dependency on org.graalvm.js:js which along with its
+ * dependency libraries are 50 mb.
+ */
+class GraalJSRegularExpression implements RegularExpression {
+    private final GraalJSRegularExpressionContext context;
+    private final Value function;
+    
+    GraalJSRegularExpression(String regex, GraalJSRegularExpressionContext context) {
+        this.context = context;
+        synchronized(context.getContext()) {
+            this.function = context.getRegExpBuilder().execute(regex);
+        }
+    }
+
+    @Override
+    public boolean matches(String value) {
+        synchronized(context.getContext()) {
+            return !function.execute(value).isNull();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java
new file mode 100644
index 0000000..14c63a8
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Value;
+
+/**
+ * GraalJSRegularExpressionContext.
+ */
+public class GraalJSRegularExpressionContext {
+    private static final String SOURCE = "pattern => {\n"
+            + "    const regex = new RegExp(pattern, 'u');\n"
+            + "    return text => text.match(regex)\n"
+            + "};";
+
+    private final Context context;
+    private final Value regExpBuilder;
+
+    /**
+     * Constructor.
+     * <p>
+     * It is the caller's responsibility to release the context when it is no longer
+     * required.
+     * 
+     * @param context the context
+     */
+    public GraalJSRegularExpressionContext(Context context) {
+        this.context = context;
+        synchronized(this.context) {
+            this.regExpBuilder = this.context.eval("js", SOURCE);
+        }
+    }
+
+    /**
+     * Operations must synchronize on the {@link Context} as only a single thread
+     * can access the {@link Context} and {@link #getRegExpBuilder()} at one time.
+     * 
+     * @return the context
+     */
+    public Context getContext() {
+        return context;
+    }
+
+    /**
+     * Gets the RegExp builder.
+     * 
+     * @return the regexp builder
+     */
+    public Value getRegExpBuilder() {
+        return regExpBuilder;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java
new file mode 100644
index 0000000..113da02
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import org.graalvm.polyglot.Context;
+
+/**
+ * GraalJS {@link RegularExpressionFactory}.
+ * <p>
+ * This requires a dependency on org.graalvm.js:js which along with its
+ * dependency libraries are 50 MB.
+ */
+public class GraalJSRegularExpressionFactory implements RegularExpressionFactory {
+    private static class Holder {
+        private static final GraalJSRegularExpressionFactory INSTANCE = new GraalJSRegularExpressionFactory();
+    }
+
+    private final GraalJSRegularExpressionContext context;
+
+    public static GraalJSRegularExpressionFactory getInstance() {
+        return Holder.INSTANCE;
+    }
+
+    /**
+     * Constructor.
+     * <p>
+     * This uses the context from {@link GraalJSContextFactory#getInstance()}.
+     * <p>
+     * It is the caller's responsibility to release the context when it is no longer
+     * required by using GraalJSContextFactory.getInstance().close().
+     */
+    public GraalJSRegularExpressionFactory() {
+        this(GraalJSContextFactory.getInstance());
+    }
+
+    /**
+     * Constructor.
+     * <p>
+     * It is the caller's responsibility to release the context when it is no longer
+     * required.
+     * 
+     * @param context the context
+     */
+    public GraalJSRegularExpressionFactory(Context context) {
+        this.context = new GraalJSRegularExpressionContext(context);
+    }
+
+    @Override
+    public RegularExpression getRegularExpression(String regex) {
+        return new GraalJSRegularExpression(regex, this.context);
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java
new file mode 100644
index 0000000..0e773a3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+/**
+ * JDK {@link RegularExpressionFactory}.
+ */
+public class JDKRegularExpressionFactory implements RegularExpressionFactory {
+    private static final JDKRegularExpressionFactory INSTANCE = new JDKRegularExpressionFactory();
+    
+    public static JDKRegularExpressionFactory getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public RegularExpression getRegularExpression(String regex) {
+        return new JDKRegularExpression(regex);
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java b/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java
index 5e205e4..21cbdd1 100644
--- a/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java
+++ b/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java
@@ -7,18 +7,37 @@ import org.jcodings.specific.UTF8Encoding;
 import org.joni.Option;
 import org.joni.Regex;
 import org.joni.Syntax;
+import org.joni.constants.SyntaxProperties;
 import org.joni.exception.SyntaxException;
 
 /**
- * ECMAScript {@link RegularExpression}.
+ * Joni {@link RegularExpression}.
+ * <p>
+ * This requires a dependency on org.jruby.joni:joni which along with its
+ * dependency libraries are 2 MB.
  */
 class JoniRegularExpression implements RegularExpression {
     private final Regex pattern;
     private final Pattern INVALID_ESCAPE_PATTERN = Pattern.compile(
-            ".*\\\\([aeg-moqyzACE-OQ-RT-VX-Z1-9]|c$|[pP]([^{]|$)|u([^{0-9]|$)|x([0-9a-fA-F][^0-9a-fA-F]|[^0-9a-fA-F][0-9a-fA-F]|[^0-9a-fA-F][^0-9a-fA-F]|.?$)).*");
+            ".*\\\\([aeg-jl-moqyzACE-OQ-RT-VX-Z1-9]|k([^<]|$)|c$|[pP]([^{]|$)|u([^{0-9]|$)|x([0-9a-fA-F][^0-9a-fA-F]|[^0-9a-fA-F][0-9a-fA-F]|[^0-9a-fA-F][^0-9a-fA-F]|.?$)).*");
+    
+    /**
+     * This is a custom syntax as Syntax.ECMAScript doesn't seem to be correct.
+     * <p>
+     * 
+     * @see <a href=
+     *      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group">OP2_QMARK_LT_NAMED_GROUP</a>
+     * @see <a href=
+     *      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_backreference">OP2_ESC_K_NAMED_BACKREF</a>
+     */
+    private static final Syntax SYNTAX = new Syntax(Syntax.ECMAScript.name, Syntax.ECMAScript.op,
+            Syntax.ECMAScript.op2 | SyntaxProperties.OP2_QMARK_LT_NAMED_GROUP
+                    | SyntaxProperties.OP2_ESC_K_NAMED_BACKREF,
+            Syntax.ECMAScript.op3, Syntax.ECMAScript.behavior, Syntax.ECMAScript.options,
+            Syntax.ECMAScript.metaCharTable);;
 
     JoniRegularExpression(String regex) {
-        this(regex, Syntax.ECMAScript);
+        this(regex, SYNTAX);
     }
 
     JoniRegularExpression(String regex, Syntax syntax) {
diff --git a/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java
new file mode 100644
index 0000000..2d451ae
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+/**
+ * Joni {@link RegularExpressionFactory}.
+ * <p>
+ * This requires a dependency on org.jruby.joni:joni which along with its
+ * dependency libraries are 2 MB.
+ */
+public class JoniRegularExpressionFactory implements RegularExpressionFactory {
+    private static final JoniRegularExpressionFactory INSTANCE = new JoniRegularExpressionFactory();
+
+    public static JoniRegularExpressionFactory getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public RegularExpression getRegularExpression(String regex) {
+        return new JoniRegularExpression(regex);
+    }
+}
diff --git a/src/main/java/com/networknt/schema/regex/RegularExpression.java b/src/main/java/com/networknt/schema/regex/RegularExpression.java
index 83f4d22..8a3140b 100644
--- a/src/main/java/com/networknt/schema/regex/RegularExpression.java
+++ b/src/main/java/com/networknt/schema/regex/RegularExpression.java
@@ -11,9 +11,7 @@ public interface RegularExpression {
 
     static RegularExpression compile(String regex, ValidationContext validationContext) {
         if (null == regex) return s -> true;
-        return validationContext.getConfig().isEcma262Validator()
-            ? new JoniRegularExpression(regex)
-            : new JDKRegularExpression(regex);
+        return validationContext.getConfig().getRegularExpressionFactory().getRegularExpression(regex);
     }
 
 }
\ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java
new file mode 100644
index 0000000..5aca068
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+/**
+ * Factory for {@link RegularExpression}.
+ */
+public interface RegularExpressionFactory {
+    /**
+     * Gets a {@link RegularExpression}.
+     * 
+     * @param regex the regular expression text value
+     * @return the regular expression
+     */
+    RegularExpression getRegularExpression(String regex);
+}


Test patch:
diff --git a/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java b/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java
index 3852f63..4e9e8f1 100644
--- a/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java
+++ b/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java
@@ -18,6 +18,8 @@ package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.regex.JDKRegularExpressionFactory;
+import com.networknt.schema.regex.JoniRegularExpressionFactory;
 import com.networknt.schema.serialization.JsonMapperFactory;
 import com.networknt.schema.suite.TestCase;
 import com.networknt.schema.suite.TestSource;
@@ -206,7 +208,9 @@ public abstract class AbstractJsonSchemaTestSuite extends HTTPServiceSupport {
 
         SchemaValidatorsConfig config = new SchemaValidatorsConfig();
         config.setTypeLoose(typeLoose);
-        config.setEcma262Validator(TestSpec.RegexKind.JDK != testSpec.getRegex());
+        config.setRegularExpressionFactory(
+                TestSpec.RegexKind.JDK == testSpec.getRegex() ? JDKRegularExpressionFactory.getInstance()
+                        : JoniRegularExpressionFactory.getInstance());
         testSpec.getStrictness().forEach(config::setStrict);
 
         if (testSpec.getConfig() != null) {
diff --git a/src/test/java/com/networknt/schema/CachedSupplierTest.java b/src/test/java/com/networknt/schema/CachedSupplierTest.java
new file mode 100644
index 0000000..f0b8cfa
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CachedSupplierTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for CachedSupplier.
+ */
+class CachedSupplierTest {
+    @Test
+    void nullValue() {
+        CachedSupplier<String> supplier = new CachedSupplier<>(null);
+        assertNull(supplier.get());
+    }
+
+    @Test
+    void concurrency() throws Exception {
+        AtomicInteger value = new AtomicInteger(0);
+        
+        CachedSupplier<Integer> supplier = new CachedSupplier<>(() -> {
+            return value.addAndGet(1);
+        });
+        Exception[] instance = new Exception[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        List<Thread> threads = new ArrayList<>();
+        for (int i = 0; i < 50; ++i) {
+            Runnable runner = new Runnable() {
+                public void run() {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                    try {
+                        int result = supplier.get();
+                        assertEquals(1, result);
+                    } catch(Exception e) {
+                        synchronized(instance) {
+                            instance[0] = e;
+                        }
+                    }
+                }
+            };
+            Thread thread = new Thread(runner, "Thread" + i);
+            thread.start();
+            threads.add(thread);
+        }
+        latch.countDown(); // Release the latch for threads to run concurrently
+        threads.forEach(t -> {
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        if (instance[0] != null) {
+            throw instance[0];
+        }
+        assertEquals(1, value.get());
+    }
+}
diff --git a/src/test/java/com/networknt/schema/ConstValidatorTest.java b/src/test/java/com/networknt/schema/ConstValidatorTest.java
new file mode 100644
index 0000000..0d22c2d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ConstValidatorTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2023 the original author or 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 com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.i18n.ResourceBundleMessageSource;
+
+/**
+ * Test for ConstValidator.
+ */
+class ConstValidatorTest {
+
+    @Test
+    void localeMessageOthers() {
+        String schemaData = "{\r\n"
+                + "  \"const\": \"aa\"\r\n"
+                + "}";
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        config.setMessageSource(new ResourceBundleMessageSource("const-messages-override", "jsv-messages"));
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        String inputData = "\"bb\"";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertEquals("$: must be the constant value 'aa' but is 'bb'", messages.iterator().next().getMessage());
+    }
+
+    @Test
+    void localeMessageNumber() {
+        String schemaData = "{\r\n"
+                + "  \"const\": 1\r\n"
+                + "}";
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        config.setMessageSource(new ResourceBundleMessageSource("const-messages-override", "jsv-messages"));
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        String inputData = "2";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertEquals("$: must be the constant value '1' but is '2'", messages.iterator().next().getMessage());
+    }
+
+    @Test
+    void validOthers() {
+        String schemaData = "{\r\n"
+                + "  \"const\": \"aa\"\r\n"
+                + "}";
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        String inputData = "\"aa\"";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void validNumber() {
+        String schemaData = "{\r\n"
+                + "  \"const\": 1234.56789\r\n"
+                + "}";
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        String inputData = "1234.56789";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void invalidNumber() {
+        String schemaData = "{\r\n"
+                + "  \"const\": 1234.56789\r\n"
+                + "}";
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        String inputData = "\"1234.56789\"";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertFalse(messages.isEmpty());
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/OneOfValidatorTest.java b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
index 151632d..70b4f13 100644
--- a/src/test/java/com/networknt/schema/OneOfValidatorTest.java
+++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
@@ -17,8 +17,11 @@
 package com.networknt.schema;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -139,4 +142,146 @@ public class OneOfValidatorTest {
         JsonSchemaException ex = assertThrows(JsonSchemaException.class, () -> factory.getSchema(schemaData));
         assertEquals("type", ex.getValidationMessage().getMessageKey());
     }
+
+    /**
+     * This test checks that the oneOf example at
+     * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
+     * behaves according to the specification instead of the example.
+     *
+     * https://github.com/swagger-api/swagger.io/issues/253
+     * https://github.com/OAI/OpenAPI-Specification/issues/3477
+     * https://github.com/networknt/json-schema-validator/issues/110
+     */
+    @Test
+    void invalidSwaggerIoExample() {
+        String document = "paths:\r\n"
+                + "  /pets:\r\n"
+                + "    patch:\r\n"
+                + "      requestBody:\r\n"
+                + "        content:\r\n"
+                + "          application/json:\r\n"
+                + "            schema:\r\n"
+                + "              oneOf:\r\n"
+                + "                - $ref: '#/components/schemas/Cat'\r\n"
+                + "                - $ref: '#/components/schemas/Dog'\r\n"
+                + "      responses:\r\n"
+                + "        '200':\r\n"
+                + "          description: Updated\r\n"
+                + "components:\r\n"
+                + "  schemas:\r\n"
+                + "    Dog:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        bark:\r\n"
+                + "          type: boolean\r\n"
+                + "        breed:\r\n"
+                + "          type: string\r\n"
+                + "          enum: [Dingo, Husky, Retriever, Shepherd]\r\n"
+                + "    Cat:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        hunts:\r\n"
+                + "          type: boolean\r\n"
+                + "        age:\r\n"
+                + "          type: integer";
+        
+        JsonSchema schema = JsonSchemaFactory
+                .getInstance(VersionFlag.V202012,
+                        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders
+                                .schemas(Collections.singletonMap("http://example.org/example.yaml", document))))
+                .getSchema(SchemaLocation.of(
+                        "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema"));
+        
+        String example1 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"breed\": \"Dingo\" \r\n"
+                + "}";
+        assertFalse(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example2 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true\r\n"
+                + "}";
+        assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example3 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true,\r\n"
+                + "  \"breed\": \"Husky\",\r\n"
+                + "  \"age\": 3      \r\n"
+                + "}";
+        assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
+    }
+    
+    /**
+     * This test checks that the oneOf example at
+     * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
+     * behaves according to the specification instead of the example.
+     *
+     * https://github.com/swagger-api/swagger.io/issues/253
+     * https://github.com/OAI/OpenAPI-Specification/issues/3477
+     * https://github.com/networknt/json-schema-validator/issues/110
+     */
+    @Test
+    void fixedSwaggerIoExample() {
+        String document = "paths:\r\n"
+                + "  /pets:\r\n"
+                + "    patch:\r\n"
+                + "      requestBody:\r\n"
+                + "        content:\r\n"
+                + "          application/json:\r\n"
+                + "            schema:\r\n"
+                + "              oneOf:\r\n"
+                + "                - $ref: '#/components/schemas/Cat'\r\n"
+                + "                - $ref: '#/components/schemas/Dog'\r\n"
+                + "      responses:\r\n"
+                + "        '200':\r\n"
+                + "          description: Updated\r\n"
+                + "components:\r\n"
+                + "  schemas:\r\n"
+                + "    Dog:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        bark:\r\n"
+                + "          type: boolean\r\n"
+                + "        breed:\r\n"
+                + "          type: string\r\n"
+                + "          enum: [Dingo, Husky, Retriever, Shepherd]\r\n"
+                + "      required:\r\n"
+                + "        - bark\r\n"
+                + "        - breed\r\n"
+                + "    Cat:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        hunts:\r\n"
+                + "          type: boolean\r\n"
+                + "        age:\r\n"
+                + "          type: integer\r\n"
+                + "      required:\r\n"
+                + "        - hunts\r\n"
+                + "        - age";
+        
+        JsonSchema schema = JsonSchemaFactory
+                .getInstance(VersionFlag.V202012,
+                        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders
+                                .schemas(Collections.singletonMap("http://example.org/example.yaml", document))))
+                .getSchema(SchemaLocation.of(
+                        "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema"));
+        
+        String example1 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"breed\": \"Dingo\" \r\n"
+                + "}";
+        assertTrue(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example2 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true\r\n"
+                + "}";
+        assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example3 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true,\r\n"
+                + "  \"breed\": \"Husky\",\r\n"
+                + "  \"age\": 3      \r\n"
+                + "}";
+        assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
+    }
 }
diff --git a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
index 22059d4..c21d7ef 100644
--- a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
+++ b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
@@ -19,6 +19,7 @@ package com.networknt.schema;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.networknt.schema.SpecVersion.VersionFlag;
 import com.networknt.schema.output.OutputUnit;
+import com.networknt.schema.regex.JoniRegularExpressionFactory;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -52,7 +53,7 @@ public class PatternPropertiesValidatorTest extends BaseJsonSchemaValidatorTest
     public void testInvalidPatternPropertiesValidatorECMA262() throws Exception {
         Assertions.assertThrows(JsonSchemaException.class, () -> {
             SchemaValidatorsConfig config = new SchemaValidatorsConfig();
-            config.setEcma262Validator(true);
+            config.setRegularExpressionFactory(JoniRegularExpressionFactory.getInstance());
             JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
             JsonSchema schema = factory.getSchema("{\"patternProperties\":6}", config);
 
diff --git a/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java b/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java
new file mode 100644
index 0000000..511eac9
--- /dev/null
+++ b/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.regex.ECMAScriptRegularExpressionFactory;
+import com.networknt.schema.regex.JDKRegularExpressionFactory;
+
+/**
+ * Test for SchemaValidatorsConfig.
+ */
+class SchemaValidatorsConfigTest {
+    @Test
+    void defaultEcma262Validator() {
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        assertSame(JDKRegularExpressionFactory.getInstance(), config.getRegularExpressionFactory());
+        assertFalse(config.isEcma262Validator());
+    }
+
+    @Test
+    void setEcma262Validator() {
+        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+
+        config.setEcma262Validator(true);
+        assertSame(ECMAScriptRegularExpressionFactory.getInstance(), config.getRegularExpressionFactory());
+        assertTrue(config.isEcma262Validator());
+
+        config.setEcma262Validator(false);
+        assertSame(JDKRegularExpressionFactory.getInstance(), config.getRegularExpressionFactory());
+        assertFalse(config.isEcma262Validator());
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java
new file mode 100644
index 0000000..15eaad3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for ECMAScriptRegularExpressionFactory. 
+ */
+class ECMAScriptRegularExpressionFactoryTest {
+
+    @Test
+    void shouldPreferGraalJS() {
+        RegularExpression regularExpression = ECMAScriptRegularExpressionFactory.getInstance()
+                .getRegularExpression("[a-z]");
+        assertInstanceOf(GraalJSRegularExpression.class, regularExpression);
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java
new file mode 100644
index 0000000..a1c4de0
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.graalvm.polyglot.Context;
+import org.junit.jupiter.api.Test;
+
+/**
+ * GraalJSRegularExpressionFactoryTest.
+ */
+class GraalJSRegularExpressionFactoryTest {
+
+    @Test
+    void constructorContext() {
+        try (Context context = Context.newBuilder("js").option("engine.WarnInterpreterOnly", "false")
+                .build()) {
+            GraalJSRegularExpressionFactory factory = new GraalJSRegularExpressionFactory(context);
+            RegularExpression regex = factory.getRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?");
+            assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+        }
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java
new file mode 100644
index 0000000..1c1fd7d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Test for GraalJSRegularExpression.
+ */
+class GraalJSRegularExpressionTest {
+    private static GraalJSRegularExpressionContext CONTEXT = new GraalJSRegularExpressionContext(
+            GraalJSContextFactory.getInstance());
+
+    enum InvalidEscapeInput {
+        A("\\a"),
+        HELLOA("hello\\a"),
+        C("\\c"),
+        E("\\e"),
+        G("\\g"),
+        H("\\h"),
+        I("\\i"),
+        J("\\j"),
+        K("\\k"),
+        L("\\l"),
+        M("\\m"),
+        O("\\o"),
+        Q("\\q"),
+        U("\\u"),
+        X("\\x"),
+        X1("\\x1"),
+        XGG("\\xgg"),
+        X1G("\\x1g"),
+        Y("\\y"),
+        Z("\\z"),
+        _1("\\1"),
+        _2("\\2"),
+        _3("\\3"),
+        _4("\\4"),
+        _5("\\5"),
+        _6("\\6"),
+        _7("\\7"),
+        _8("\\8"),
+        _9("\\9");
+
+        String value;
+
+        InvalidEscapeInput(String value) {
+            this.value = value;
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(InvalidEscapeInput.class)
+    void invalidEscape(InvalidEscapeInput input) {
+        RuntimeException e = assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression(input.value, CONTEXT));
+        assertTrue(e.getMessage().startsWith("SyntaxError"));
+    }
+
+    enum ValidEscapeInput {
+        B("\\b"),
+        D("\\d"),
+        CAP_D("\\D"),
+        W("\\w"),
+        CAP_W("\\W"),
+        S("\\s"),
+        CAP_S("\\S"),
+        T("\\t"),
+        U1234("\\u1234"),
+        R("\\r"),
+        N("\\n"),
+        V("\\v"),
+        F("\\f"),
+        X12("\\x12"),
+        X1F("\\x1f"),
+        X1234("\\x1234"),
+        P("\\p{Letter}cole"), // unicode property
+        CAP_P("\\P{Letter}cole"), // unicode property
+        _0("\\0"),
+        CA("\\cA"), // control
+        CB("\\cB"), // control
+        CC("\\cC"), // control
+        CG("\\cG"); // control
+
+        String value;
+
+        ValidEscapeInput(String value) {
+            this.value = value;
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(ValidEscapeInput.class)
+    void validEscape(ValidEscapeInput input) {
+        assertDoesNotThrow(() -> new GraalJSRegularExpression(input.value, CONTEXT));
+    }
+    
+    @Test
+    void invalidPropertyName() {
+        assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\p", CONTEXT));
+        assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\P", CONTEXT));
+        assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\pa", CONTEXT));
+        assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\Pa", CONTEXT));
+    }
+    
+    @Test
+    void digit() {
+        RegularExpression regex = new GraalJSRegularExpression("\\d", CONTEXT);
+        assertTrue(regex.matches("1"));
+        assertFalse(regex.matches("a"));
+    }
+
+    @Test
+    void invalidEscape() {
+        RuntimeException e = assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\a", CONTEXT));
+        assertEquals("SyntaxError: Invalid escape", e.getMessage());
+    }
+
+    @Test
+    void namedCapturingGroup() {
+        RegularExpression regex = new GraalJSRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?", CONTEXT);
+        assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+    }
+
+    @Test
+    void invalidNamedCapturingGroup() {
+        assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("(?<name>)(?<name>)", CONTEXT));
+    }
+
+    @Test
+    void namedBackreference() {
+        RegularExpression regex = new GraalJSRegularExpression("title=(?<quote>[\"'])(.*?)\\k<quote>", CONTEXT);
+        assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\""));
+    }
+
+    @Test
+    void concurrency() throws Exception {
+        RegularExpression regex = new GraalJSRegularExpression("\\d", CONTEXT);
+        Exception[] instance = new Exception[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        List<Thread> threads = new ArrayList<>();
+        for (int i = 0; i < 50; ++i) {
+            Runnable runner = new Runnable() {
+                public void run() {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                    try {
+                        assertTrue(regex.matches("1"));
+                    } catch (RuntimeException e) {
+                        instance[0] = e;
+                    }
+                }
+            };
+            Thread thread = new Thread(runner, "Thread" + i);
+            thread.start();
+            threads.add(thread);
+        }
+        latch.countDown(); // Release the latch for threads to run concurrently
+        threads.forEach(t -> {
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        if (instance[0] != null) {
+            throw instance[0];
+        }
+    }
+}
diff --git a/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java
new file mode 100644
index 0000000..76fcc5f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * JDKRegularExpressionFactoryTest.
+ */
+class JDKRegularExpressionFactoryTest {
+
+    @Test
+    void getRegularExpression() {
+        JDKRegularExpressionFactory factory = new JDKRegularExpressionFactory();
+        RegularExpression regex = factory
+                .getRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?");
+        assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+    }
+
+    @Test
+    void getInstance() {
+        assertSame(JDKRegularExpressionFactory.getInstance(), JDKRegularExpressionFactory.getInstance());
+    }
+}
diff --git a/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java
new file mode 100644
index 0000000..8cca8a7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for JDKRegularExpression.
+ */
+class JDKRegularExpressionTest {
+    @Test
+    void namedCapturingGroup() {
+        RegularExpression regex = new JDKRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?");
+        assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+    }
+
+    @Test
+    void invalidNamedCapturingGroup() {
+        assertThrows(RuntimeException.class, () -> new JDKRegularExpression("(?<name>)(?<name>)"));
+    }
+
+    @Test
+    void namedBackreference() {
+        RegularExpression regex = new JDKRegularExpression("title=(?<quote>[\"'])(.*?)\\k<quote>");
+        assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\""));
+    }
+}
diff --git a/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java
new file mode 100644
index 0000000..fb9c19b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2024 the original author or 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 com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * JoniRegularExpressionFactoryTest.
+ */
+class JoniRegularExpressionFactoryTest {
+
+    @Test
+    void getRegularExpression() {
+        JoniRegularExpressionFactory factory = new JoniRegularExpressionFactory();
+        RegularExpression regex = factory
+                .getRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?");
+        assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+    }
+
+    @Test
+    void getInstance() {
+        assertSame(JoniRegularExpressionFactory.getInstance(), JoniRegularExpressionFactory.getInstance());
+    }
+}
diff --git a/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java
index fc1f899..9a1d0cd 100644
--- a/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java
+++ b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java
@@ -18,6 +18,7 @@ package com.networknt.schema.regex;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.joni.exception.SyntaxException;
 import org.junit.jupiter.api.Test;
@@ -111,7 +112,7 @@ class JoniRegularExpressionTest {
     void validEscape(ValidEscapeInput input) {
         assertDoesNotThrow(() -> new JoniRegularExpression(input.value));
     }
-    
+
     @Test
     void invalidPropertyName() {
         assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\p"));
@@ -119,4 +120,37 @@ class JoniRegularExpressionTest {
         assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\pa"));
         assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\Pa"));
     }
+
+    /**
+     * Named capturing group: (?<name>...).
+     * 
+     * @see org.joni.constants.SyntaxProperties#OP2_QMARK_LT_NAMED_GROUP.
+     * @see <a href=
+     *      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group">Named
+     *      capturing group</a>
+     */
+    @Test
+    void namedCapturingGroup() {
+        RegularExpression regex = new JoniRegularExpression("((?<OrgOID>[^,. ]+)\\s*\\.\\s*(?<AOID>[^,. ]+))(?:\\s*,\\s*)?");
+        assertTrue(regex.matches("FFFF.12645,AAAA.6456"));
+    }
+
+    @Test
+    void invalidNamedCapturingGroup() {
+        assertThrows(RuntimeException.class, () -> new JoniRegularExpression("(?<name>)(?<name>)"));
+    }
+
+    /**
+     * Named capturing group: (?<name>...).
+     * 
+     * @see org.joni.constants.SyntaxProperties#OP2_ESC_K_NAMED_BACKREF.
+     * @see <a href=
+     *      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_backreference">Named
+     *      backreference</a>
+     */
+    @Test
+    void namedBackreference() {
+        RegularExpression regex = new JoniRegularExpression("title=(?<quote>[\"'])(.*?)\\k<quote>");
+        assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\""));
+    }
 }
diff --git a/src/test/resources/const-messages-override.properties b/src/test/resources/const-messages-override.properties
new file mode 100644
index 0000000..247e952
--- /dev/null
+++ b/src/test/resources/const-messages-override.properties
@@ -0,0 +1,1 @@
+const = {0}: must be the constant value ''{1}'' but is ''{2}''
\ No newline at end of file