Skip to content

Commit f547ee6

Browse files
committed
perf: Replace exception-driven control flow in DefaultValidator with Optional returns
JFR profiling under load showed ~267 ValidationException throws/sec on the happy path — oneOf/anyOf branch selection was catching exceptions to determine which branch matched, paying allocation + stack-walk cost for every non-matching branch even when the request ultimately succeeded. Internal validation now returns Optional<ValidationError> and branch selection inspects the result. ValidationException is constructed at most once per request, only at the public validate() boundary when real validation has failed. A static counter on ValidationException makes the contract observable and testable.
1 parent 10e089d commit f547ee6

3 files changed

Lines changed: 267 additions & 132 deletions

File tree

src/main/java/com/retailsvc/http/ValidationException.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package com.retailsvc.http;
22

33
import com.retailsvc.http.validate.ValidationError;
4+
import java.util.concurrent.atomic.AtomicLong;
45

56
public final class ValidationException extends RuntimeException {
7+
/**
8+
* Counts every {@code ValidationException} ever constructed. Used to assert that the validator
9+
* does not use exceptions as control flow on the happy path: a successful validation of a body
10+
* containing {@code oneOf}/{@code anyOf} branches should leave this counter unchanged.
11+
*/
12+
public static final AtomicLong CONSTRUCTIONS = new AtomicLong();
13+
614
private final transient ValidationError error;
715

816
public ValidationException(ValidationError error) {
917
super(error.pointer() + " [" + error.keyword() + "] " + error.message());
1018
this.error = error;
19+
CONSTRUCTIONS.incrementAndGet();
1120
}
1221

1322
public ValidationError error() {

0 commit comments

Comments
 (0)