Bad validator performance when adding obsolete @Validated to Java value objects

Description

I ran into a performance issue with Java Bean Validation (Hibernate Validator) on a large object, which caused the validation to run several orders slower.

I was able to isolate and reproduce it with the following example code:

Example DTOs

Let's say we have a Order with Items:

The Items look like this:

Bean Validation and Benchmark

Let's create an order with 20000 items, and benchmark how long it takes to validate it (while also verifying that it is validated):

Benchmark times:

The issue

Obviously, the @Valid annotations on the Item id (Long) and quantity (int) are obsolete (this annotation hints that the validation should recurse into a bean or collection, which neither of these values are). If I remove them and run the validation benchmark again, the times look good (while still having the correct validation outcome):

 

This basically affects all value objects, where a @Valid was annotated:

  • String and Character

  • all Number classes (Integer, Long, …, BigInteger, BigDecimal)

  • all enum types

  • Java Date/Time value objects (LocalDate, LocalTime, LocalDateTime, OffsetDateTime, …)

  • other Java value objects ( UUID, InetAddress, …)

The performance impact is non-linear, and seems to grow exponentially with the number of items in that list.

Expectation: the @Valid should be ignored for Java-internal value objects (as listed above), as it is obsolete, and has a critical performance impact.

Environment

Java 11, Linux, Hibernate Validator 6.2.0.Final

Attachments

2

Activity

Show:

Anatoliy BalakirevJanuary 10, 2023 at 5:05 PM

Just to give more use-cases to this issue: we now have exactly the same problem for beans, generated by the “openapi-generator-maven-plugin”. I’ve described it here: And in our case the fix is not so trivial, as it will have to go to that plugin eventually.

Peter WalserNovember 11, 2022 at 11:16 AM

Profiled with JVisualVM:

Almost all of the time is burnt in AbstractValidationContext.isAlreadyValidatedForPath calling AbstractValidationContext.isSubPathOf.

Peter WalserNovember 11, 2022 at 11:04 AM
Edited

When I wrap the values into value objects (with @Valid), the validation runs very fast (although it supposedly does the same: recurse into values with @Valid and validate with @NotNull and @Min ):

0 items, avg. validation time: 49.520402 ms
1 items, avg. validation time: 50.609528 ms
2 items, avg. validation time: 50.88673 ms
5 items, avg. validation time: 50.236684 ms
10 items, avg. validation time: 49.789304 ms
20 items, avg. validation time: 48.713483 ms
50 items, avg. validation time: 50.069192 ms
100 items, avg. validation time: 50.027649 ms
200 items, avg. validation time: 49.037075 ms
500 items, avg. validation time: 50.690123 ms
1000 items, avg. validation time: 50.057061 ms
2500 items, avg. validation time: 49.99966 ms
5000 items, avg. validation time: 48.518808 ms
7500 items, avg. validation time: 50.0492 ms
10000 items, avg. validation time: 50.01165 ms
20000 items, avg. validation time: 48.984054 ms
30000 items, avg. validation time: 51.152249 ms
40000 items, avg. validation time: 50.320894 ms
50000 items, avg. validation time: 50.039272 ms

That would actually be the expected outcome if I add a @Validated onto a value object: a slight overhead, but no exponential growth.

Peter WalserNovember 11, 2022 at 10:55 AM

The validation time also grows exponentially with the number of items:

0 items, avg. validation time: 0.078653 ms
1 items, avg. validation time: 0.233635 ms
2 items, avg. validation time: 0.277518 ms
5 items, avg. validation time: 0.442096 ms
10 items, avg. validation time: 0.386543 ms
20 items, avg. validation time: 0.352659 ms
50 items, avg. validation time: 0.69204 ms
100 items, avg. validation time: 1.090923 ms
200 items, avg. validation time: 2.762619 ms
500 items, avg. validation time: 7.474321 ms
1000 items, avg. validation time: 27.675976 ms
2500 items, avg. validation time: 184.583688 ms
5000 items, avg. validation time: 624.382551 ms
7500 items, avg. validation time: 1262.320178 ms
10000 items, avg. validation time: 2125.455512 ms
20000 items, avg. validation time: 8322.531352 ms
30000 items, avg. validation time: 19281.462 ms

Guillaume SmetNovember 9, 2022 at 1:22 PM

I’m not sure “obsolete” is the right word. @Valid was always supposed to only be used to cascade the validation to beans. You should only use it when you want the validation to cascade. The way you used it has always been not recommended at all.

I’m not sure we can really ignore internal types as we could imagine someone adding constraints to these types via the programmatic API. Even if it’s far fetched, it would still be a valid use case of Hibernate Validator.

Now I wonder if we could optimize the case if we don’t have any constraints for this particular class. I’m not sure we have the information when gathering the metadata though.

And really, you shouldn’t have used @Valid in the first place so that’s an easy fix.

We are making progress to disable bean tracking in certain cases but it will only be doable for projects using Quarkus (or a similar infrastructure) as it requires to live in a closed world assumption and have the list of all the potentially validated beans.

Now, it might be a good idea to run your benchmark with async profiler and share the flame graphs. There might be some improvements we could do.

Details

Assignee

Reporter

Bug Testcase Reminder (view)

Bug reports should generally be accompanied by a test case!

Bug Testcase Reminder (edit)

Bug reports should generally be accompanied by a test case!

backPortable

Backport?

Participants

Anatoliy Balakirev
Guillaume Smet
Peter Walser

Components

Affects versions

Priority

Created November 8, 2022 at 10:18 AM
Updated June 13, 2024 at 11:54 AM