Avoid memory leaks in child
Avoid non needed work when no validation constraints are found
Small micro-benchmarks
Fix Java 11 compilation with Java FX as an external library
diff --git a/bval-jsr/pom.xml b/bval-jsr/pom.xml
index 4efe51d..4bc0bde 100644
--- a/bval-jsr/pom.xml
+++ b/bval-jsr/pom.xml
@@ -45,6 +45,11 @@
             </activation>
             <dependencies>
                 <dependency>
+                    <groupId>org.apache.geronimo.specs</groupId>
+                    <artifactId>geronimo-activation_1.1_spec</artifactId>
+                    <version>1.1</version>
+                </dependency>
+                <dependency>
                     <groupId>javax.xml.bind</groupId>
                     <artifactId>jaxb-api</artifactId>
                     <version>2.3.0</version>
@@ -262,6 +267,28 @@
             <version>2.2.7</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+            <version>6.1.5.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-core</artifactId>
+            <version>1.25.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-generator-annprocess</artifactId>
+            <version>1.25.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.biboudis</groupId>
+            <artifactId>jmh-profilers</artifactId>
+            <version>0.1.4</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -299,6 +326,13 @@
 	                    <source>${project.basedir}/src/main/xsd/validation-mapping-2.0.xsd</source>
                     </sources>
                 </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.geronimo.specs</groupId>
+                        <artifactId>geronimo-activation_1.1_spec</artifactId>
+                        <version>1.1</version>
+                    </dependency>
+                </dependencies>
             </plugin>
 
             <!-- create mainClass attribute -->
@@ -431,6 +465,14 @@
                     </execution>
                 </executions>
            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>9</source>
+                    <target>9</target>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/ParticipantFactory.java b/bval-jsr/src/main/java/org/apache/bval/jsr/ParticipantFactory.java
index 1975fe4..006bbcd 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/ParticipantFactory.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/ParticipantFactory.java
@@ -72,7 +72,7 @@
         return newInstance(loadClass(classname));
     }
 
-    <T> Set<T> loadServices(Class<T> type) {
+    <T> Set<T> loadServices(Class<T> type) { // todo: enable somehow to cache shared classloader (think tomcat/tomee)
         Validate.notNull(type);
         final Set<URL> resources = new LinkedHashSet<>();
         final String resourceName = META_INF_SERVICES + type.getName();
@@ -85,7 +85,7 @@
                 log.log(Level.SEVERE, "Error searching for resource(s) " + resourceName, e);
             }
         }
-        return resources.stream().map(this::read).flatMap(Collection::stream).<T> map(this::create)
+        return resources.stream().distinct().map(this::read).flatMap(Collection::stream).<T> map(this::create)
             .collect(ToUnmodifiable.set());
     }
 
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
index caa9bb3..ef4f953 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
@@ -60,7 +60,6 @@
     private final ApacheValidatorFactory validatorFactory;
     private final ConcurrentMap<Class<?>, BeanD<?>> beanDescriptors = new ConcurrentHashMap<>();
     // synchronization unnecessary
-    private final Set<Class<?>> knownUnconstrainedTypes = new HashSet<>();
     private final ReflectionBuilder reflectionBuilder;
 
     public DescriptorManager(ApacheValidatorFactory validatorFactory) {
@@ -73,25 +72,13 @@
         Validate.notNull(beanClass, IllegalArgumentException::new, "beanClass");
 
         // cannot use computeIfAbsent due to recursion being the usual case:
-        if (beanDescriptors.containsKey(beanClass)) {
-            return beanDescriptors.get(beanClass);
+        final BeanD<?> existing = beanDescriptors.get(beanClass);
+        if (existing != null) {
+            return existing;
         }
-        final boolean constrained = !knownUnconstrainedTypes.contains(beanClass);
-        final MetadataBuilder.ForBean<T> builder = constrained ? builder(beanClass) : EmptyBuilder.instance().forBean();
-        final BeanD<T> beanD = new BeanD<>(new MetadataReader(validatorFactory, beanClass).forBean(builder));
-
-        if (constrained) {
-            // if not previously known to be unconstrained, check:
-            if (beanD.isBeanConstrained() || !(beanD.getConstrainedConstructors().isEmpty()
-                    && beanD.getConstrainedMethods(MethodType.GETTER, MethodType.NON_GETTER).isEmpty())) {
-                @SuppressWarnings("unchecked")
-                final BeanD<T> result =
-                Optional.ofNullable((BeanD<T>) beanDescriptors.putIfAbsent(beanClass, beanD)).orElse(beanD);
-                return result;
-            }
-        }
-        knownUnconstrainedTypes.add(beanClass);
-        return beanD;
+        final BeanD<?> value = new BeanD<>(new MetadataReader(validatorFactory, beanClass).forBean(builder(beanClass)));
+        final BeanD<?> previous = beanDescriptors.putIfAbsent(beanClass, value);
+        return previous == null ? value : previous;
     }
 
     public void clear() {
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
index bd20548..d9899db 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
@@ -24,8 +24,11 @@
 import org.apache.bval.jsr.descriptor.BeanD;
 import org.apache.bval.jsr.descriptor.ConstraintD;
 import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.jsr.util.Proxies;
 import org.apache.bval.util.Validate;
 
+import java.util.Map;
+
 public final class ValidateBean<T> extends ValidationJob<T> {
 
     private final T bean;
@@ -36,6 +39,20 @@
     }
 
     @Override
+    protected boolean hasWork() {
+        final Class<?> beanClass = bean.getClass();
+        final Map<Class<?>, Class<?>> classCache = validatorContext.getFactory().getUnwrappedClassCache();
+        Class<?> unwrappedClass = classCache.get(beanClass);
+        if (unwrappedClass == null) {
+            unwrappedClass = Proxies.classFor(beanClass);
+            classCache.putIfAbsent(beanClass, unwrappedClass);
+        }
+        return validatorContext.getFactory().getDescriptorManager()
+                .getBeanDescriptor(unwrappedClass)
+                .isBeanConstrained();
+    }
+
+    @Override
     protected Frame<BeanD<T>> computeBaseFrame() {
         return new BeanFrame<T>(new GraphContext(validatorContext, PathImpl.create(), bean));
     }
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
index eef57f5..25ab986 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
@@ -185,7 +185,8 @@
 
     @Override
     protected boolean hasWork() {
-        return describe() != null;
+        final ExecutableDescriptor descriptor = describe();
+        return descriptor != null && descriptor.hasConstrainedParameters();
     }
 
     protected abstract ExecutableDescriptor describe();
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
index 2cf92e3..416f7c5 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
@@ -141,7 +141,8 @@
 
     @Override
     protected boolean hasWork() {
-        return describe() != null;
+        final ExecutableDescriptor descriptor = describe();
+        return descriptor != null && descriptor.hasConstrainedReturnValue();
     }
 
     protected abstract ExecutableDescriptor describe();
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
index 3dc2bc4..7ab3ddf 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
@@ -561,7 +561,6 @@
             final Consumer<ConstraintViolation<T>> sink = results.consumer(Set::add);
 
             completedValidations = new ConcurrentHashMap<>();
-
             try {
                 baseFrame.process(groups.asStrategy(), sink);
             } finally {
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
index 89f50e3..1f0537b 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
@@ -16,6 +16,21 @@
  */
 package org.apache.bval.jsr.valueextraction;
 
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.ObjectUtils;
+import org.apache.bval.util.StringUtils;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
+import org.apache.bval.util.reflection.TypeUtils;
+
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.metadata.ValidateUnwrappedValue;
+import javax.validation.valueextraction.UnwrapByDefault;
+import javax.validation.valueextraction.ValueExtractor;
+import javax.validation.valueextraction.ValueExtractorDeclarationException;
+import javax.validation.valueextraction.ValueExtractorDefinitionException;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Type;
@@ -31,31 +46,14 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import javax.validation.ConstraintDeclarationException;
-import javax.validation.metadata.ValidateUnwrappedValue;
-import javax.validation.valueextraction.UnwrapByDefault;
-import javax.validation.valueextraction.ValueExtractor;
-import javax.validation.valueextraction.ValueExtractorDeclarationException;
-import javax.validation.valueextraction.ValueExtractorDefinitionException;
-
-import org.apache.bval.jsr.metadata.ContainerElementKey;
-import org.apache.bval.util.Exceptions;
-import org.apache.bval.util.Lazy;
-import org.apache.bval.util.ObjectUtils;
-import org.apache.bval.util.StringUtils;
-import org.apache.bval.util.Validate;
-import org.apache.bval.util.reflection.Reflection;
-import org.apache.bval.util.reflection.Reflection.Interfaces;
-import org.apache.bval.util.reflection.TypeUtils;
-
 /**
  * {@link ValueExtractor} collection of some level of a bean validation hierarchy.
  */
@@ -222,9 +220,8 @@
     }
 
     private final ValueExtractors parent;
-    private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> valueExtractors = new Lazy<>(TreeMap::new);
-    private final Lazy<Set<ValueExtractors>> children = new Lazy<>(HashSet::new);
-    private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> searchCache = new Lazy<>(HashMap::new);
+    private final Map<ContainerElementKey, ValueExtractor<?>> valueExtractors = new ConcurrentHashMap<>();
+    private final Map<ContainerElementKey, ValueExtractor<?>> searchCache = new ConcurrentHashMap<>();
     private final OnDuplicateContainerElementKey onDuplicateContainerElementKey;
 
     public ValueExtractors() {
@@ -243,7 +240,8 @@
     private ValueExtractors(ValueExtractors parent, OnDuplicateContainerElementKey onDuplicateContainerElementKey,
         Map<ContainerElementKey, ValueExtractor<?>> backingMap) {
         this(parent, onDuplicateContainerElementKey);
-        this.valueExtractors.reset(backingMap);
+        this.valueExtractors.clear();
+        this.valueExtractors.putAll(backingMap);
     }
 
     public ValueExtractors createChild() {
@@ -251,9 +249,7 @@
     }
 
     public ValueExtractors createChild(OnDuplicateContainerElementKey onDuplicateContainerElementKey) {
-        final ValueExtractors child = new ValueExtractors(this, onDuplicateContainerElementKey);
-        children.get().add(child);
-        return child;
+        return new ValueExtractors(this, onDuplicateContainerElementKey);
     }
 
     public void add(ValueExtractor<?> extractor) {
@@ -262,7 +258,7 @@
             Exceptions.raise(IllegalStateException::new, "Computed null %s for %s",
                 ContainerElementKey.class.getSimpleName(), extractor);
         }
-        final Map<ContainerElementKey, ValueExtractor<?>> m = valueExtractors.get();
+        final Map<ContainerElementKey, ValueExtractor<?>> m = valueExtractors;
         if (onDuplicateContainerElementKey == OnDuplicateContainerElementKey.EXCEPTION) {
             synchronized (this) {
                 if (m.containsKey(key)) {
@@ -274,19 +270,19 @@
         } else {
             m.put(key, extractor);
         }
-        children.optional().ifPresent(s -> s.stream().forEach(ValueExtractors::clearCache));
+        searchCache.clear();
     }
 
     public Map<ContainerElementKey, ValueExtractor<?>> getValueExtractors() {
-        final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> result = new Lazy<>(HashMap::new);
+        final Map<ContainerElementKey, ValueExtractor<?>> result = new HashMap<>();
         populate(result);
-        return result.optional().orElseGet(Collections::emptyMap);
+        return result;
     }
 
     public ValueExtractor<?> find(ContainerElementKey key) {
-        final Optional<ValueExtractor<?>> cacheHit = searchCache.optional().map(m -> m.get(key));
-        if (cacheHit.isPresent()) {
-            return cacheHit.get();
+        final ValueExtractor<?> cacheHit = searchCache.get(key);
+        if (cacheHit != null) {
+            return cacheHit;
         }
         final Map<ContainerElementKey, ValueExtractor<?>> allValueExtractors = getValueExtractors();
         if (allValueExtractors.containsKey(key)) {
@@ -299,7 +295,7 @@
         final Optional<ValueExtractor<?>> result =
             maximallySpecific(candidates.keySet(), ve -> candidates.get(ve).getContainerClass());
         if (result.isPresent()) {
-            searchCache.get().put(key, result.get());
+            searchCache.put(key, result.get());
             return result.get();
         }
         throw Exceptions.create(ConstraintDeclarationException::new, "Could not determine %s for %s",
@@ -329,12 +325,11 @@
         return result;
     }
 
-    private void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) {
-        Optional.ofNullable(parent).ifPresent(p -> p.populate(target));
-        valueExtractors.optional().ifPresent(m -> target.get().putAll(m));
+    private void populate(Map<ContainerElementKey, ValueExtractor<?>> target) {
+        if (parent != null) {
+            parent.populate(target);
+        }
+        target.putAll(valueExtractors);
     }
 
-    private void clearCache() {
-        searchCache.optional().ifPresent(Map::clear);
-    }
 }
diff --git a/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
index 7df9655..d3799b4 100644
--- a/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
+++ b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
@@ -43,6 +43,11 @@
         public AnnotatedType[] getAnnotatedActualTypeArguments() {
             return wrapArray(wrapped.getActualTypeArguments());
         }
+
+        @Override
+        public AnnotatedType getAnnotatedOwnerType() {
+            return null;
+        }
     }
 
     private static class Variable extends EmulatedAnnotatedType<TypeVariable<?>> implements AnnotatedTypeVariable {
@@ -55,6 +60,11 @@
         public AnnotatedType[] getAnnotatedBounds() {
             return wrapped.getAnnotatedBounds();
         }
+
+        @Override
+        public AnnotatedType getAnnotatedOwnerType() {
+            return null;
+        }
     }
 
     private static class Wildcard extends EmulatedAnnotatedType<WildcardType> implements AnnotatedWildcardType {
@@ -72,6 +82,11 @@
         public AnnotatedType[] getAnnotatedUpperBounds() {
             return wrapArray(wrapped.getUpperBounds());
         }
+
+        @Override
+        public AnnotatedType getAnnotatedOwnerType() {
+            return null;
+        }
     }
 
     public static EmulatedAnnotatedType<?> wrap(Type type) {
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
index b18030d..fa3d406 100644
--- a/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
@@ -178,7 +178,7 @@
         // unconstrained
         final BeanDescriptor objectDescriptor = validator.getConstraintsForClass(Object.class);
         assertNotNull(objectDescriptor);
-        assertNotSame(objectDescriptor, validator.getConstraintsForClass(Object.class));
+        assertSame(objectDescriptor, validator.getConstraintsForClass(Object.class));
     }
 
     public static class Form {
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/Jsr303Benchmark.java b/bval-jsr/src/test/java/org/apache/bval/jsr/Jsr303Benchmark.java
new file mode 100644
index 0000000..0092be7
--- /dev/null
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/Jsr303Benchmark.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr;
+
+import org.apache.bval.jsr.example.Author;
+import org.apache.bval.jsr.example.Book;
+import org.apache.bval.jsr.example.First;
+import org.apache.bval.jsr.example.Second;
+import org.hibernate.validator.HibernateValidator;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.profile.JavaFlightRecorderProfiler;
+import org.openjdk.jmh.profile.Profiler;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Set;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@State(Scope.Benchmark)
+public class Jsr303Benchmark {
+
+    private final ValidatorFactory bvalFactory =
+        Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory();
+    private final Validator bvalValidator = bvalFactory.getValidator();
+    private final ValidatorFactory hibernateFactory =
+        Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory();
+    private final Validator hibernateValidator = hibernateFactory.getValidator();
+
+    public static void main(String[] args) throws RunnerException {
+        final Options opt = new OptionsBuilder()
+            .include(Jsr303Benchmark.class.getSimpleName())
+
+            .forks(1)
+            .threads(5)
+
+            .measurementIterations(1)
+            .measurementTime(TimeValue.seconds(20))
+
+            .warmupIterations(2)
+            .warmupTime(TimeValue.seconds(5))
+
+            .addProfiler(JavaFlightRecorderProfiler.class)
+
+            .build();
+
+        new Runner(opt).run();
+    }
+
+    @Benchmark
+    public void bvalNoConstraints() {
+        final Set<ConstraintViolation<BookNoConstraints>> constraintViolations =
+            bvalFactory.getValidator().validate(new BookNoConstraints());
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void bvalNoConstraintsReuseValidator() {
+        final Set<ConstraintViolation<BookNoConstraints>> constraintViolations =
+            bvalValidator.validate(new BookNoConstraints());
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void bvalConstraintsSuccess() {
+        final Set<ConstraintViolation<BookSimple>> constraintViolations = bvalFactory.getValidator()
+                                                                                     .validate(new BookSimple("Hello",
+                                                                                                              "Awesome validation",
+                                                                                                              3,
+                                                                                                              76));
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void bvalConstraintsSuccessReuseValidator() {
+        final Set<ConstraintViolation<BookSimple>> constraintViolations = bvalValidator
+                                                                                     .validate(new BookSimple("Hello",
+                                                                                                              "Awesome validation",
+                                                                                                              3,
+                                                                                                              76));
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void bvalConstraintsFailure() {
+        final Set<ConstraintViolation<Book>> constraintViolations = bvalFactory.getValidator().validate(new Book());
+        assertTrue(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void bvalConstraintsFailureReuseValidator() {
+        final Set<ConstraintViolation<Book>> constraintViolations = bvalValidator.validate(new Book());
+        assertTrue(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateNoConstraints() {
+        final Set<ConstraintViolation<BookNoConstraints>> constraintViolations =
+            hibernateFactory.getValidator().validate(new BookNoConstraints());
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateNoConstraintsReuseValidator() {
+        final Set<ConstraintViolation<BookNoConstraints>> constraintViolations =
+            hibernateValidator.validate(new BookNoConstraints());
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateConstraintsSuccess() {
+        final Set<ConstraintViolation<BookSimple>> constraintViolations = hibernateFactory.getValidator()
+                                                                                          .validate(
+                                                                                              new BookSimple("Hello",
+                                                                                                             "Awesome" +
+                                                                                                             " validation",
+                                                                                                             3,
+                                                                                                             76));
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateConstraintsSuccessReuseValidator() {
+        final Set<ConstraintViolation<BookSimple>> constraintViolations = hibernateValidator
+                                                                                          .validate(
+                                                                                              new BookSimple("Hello",
+                                                                                                             "Awesome" +
+                                                                                                             " validation",
+                                                                                                             3,
+                                                                                                             76));
+        assertFalse(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateConstraintsFailure() {
+        final Set<ConstraintViolation<Book>> constraintViolations =
+            hibernateFactory.getValidator().validate(new Book());
+        assertTrue(constraintViolations.iterator().hasNext());
+    }
+
+    @Benchmark
+    public void hibernateConstraintsFailureReuseValidator() {
+        final Set<ConstraintViolation<Book>> constraintViolations =
+            hibernateValidator.validate(new Book());
+        assertTrue(constraintViolations.iterator().hasNext());
+    }
+
+
+    public static class BookNoConstraints {
+        private String title;
+        private String subtitle;
+        private Author author;
+        private int uselessField;
+        private int unconstraintField;
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(final String title) {
+            this.title = title;
+        }
+
+        public String getSubtitle() {
+            return subtitle;
+        }
+
+        public void setSubtitle(final String subtitle) {
+            this.subtitle = subtitle;
+        }
+
+        public Author getAuthor() {
+            return author;
+        }
+
+        public void setAuthor(final Author author) {
+            this.author = author;
+        }
+
+        public int getUselessField() {
+            return uselessField;
+        }
+
+        public void setUselessField(final int uselessField) {
+            this.uselessField = uselessField;
+        }
+
+        public int getUnconstraintField() {
+            return unconstraintField;
+        }
+
+        public void setUnconstraintField(final int unconstraintField) {
+            this.unconstraintField = unconstraintField;
+        }
+    }
+
+    public static class BookSimple {
+
+        @NotEmpty(groups = First.class)
+        private String title;
+
+        @Size(max = 30, groups = Second.class)
+        private String subtitle;
+
+        @NotNull
+        private int uselessField;
+
+        private int unconstraintField;
+
+        public BookSimple(
+            final String title,
+            final String subtitle,
+            final int uselessField,
+            final int unconstraintField) {
+
+            this.title = title;
+            this.subtitle = subtitle;
+            this.uselessField = uselessField;
+            this.unconstraintField = unconstraintField;
+        }
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(final String title) {
+            this.title = title;
+        }
+
+        public String getSubtitle() {
+            return subtitle;
+        }
+
+        public void setSubtitle(final String subtitle) {
+            this.subtitle = subtitle;
+        }
+
+        public int getUselessField() {
+            return uselessField;
+        }
+
+        public void setUselessField(final int uselessField) {
+            this.uselessField = uselessField;
+        }
+
+        public int getUnconstraintField() {
+            return unconstraintField;
+        }
+
+        public void setUnconstraintField(final int unconstraintField) {
+            this.unconstraintField = unconstraintField;
+        }
+    }
+
+}