Skip to content

Validator Lifecycle

Jin Kwon edited this page Nov 13, 2026 · 1 revision

Validator Lifecycle

The assertions which run validation — assertThatBean, assertThatProperty, assertThatConstructor — extend AbstractValidationAssert, which holds exactly three things: the groups, and either a Validator or a ValidatorFactory supplied by the caller. Assertions on results (violations, paths, descriptors) never touch a validator and have no such configuration.

The three cases

Configured What each validating call does Who owns it Closed by this library
usingValidator(Validator) uses that validator as is the caller — (a validator has no close())
usingValidatorFactory(ValidatorFactory) takes one Validator from the factory with getValidator() the caller never
neither builds a factory with Validation.buildDefaultValidatorFactory(), takes a validator, and closes the factory before returning the assertion yes, per call

In no case is a resource kept past the call.

// caller-owned factory: one bootstrap for many assertions
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
    assertThatBean(user).usingValidatorFactory(factory).isValid();
    assertThatBean(other).usingValidatorFactory(factory).isNotValid();
}   // the caller closes it, here

// caller-owned validator, used while its factory is open
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
    final Validator validator = factory.getValidator();
    assertThatBean(user).usingValidator(validator).isValid();
}

Ownership

  • usingValidatorFactory — the assertion never calls close() on the factory. The tests wrap a factory in a proxy whose close() fails the test, to pin this.
  • usingValidator — a Validator belongs to the factory that produced it, and the specification forbids using it after that factory is closed. The assertion does not and cannot check this; keeping the factory open for as long as the validator is in use is the caller's job. Hibernate Validator does not reject a validator from a closed factory, so this mistake passes silently with the tested provider.
  • neither — the factory exists only for the duration of one validating call. Building it costs a classpath scan plus a META-INF/validation.xml parse, once per assertion method called. A suite which minds that supplies its own factory or validator.

Why usingValidator exists alongside usingValidatorFactory: a factory yields a validator, but a validator cannot yield its factory. A validator customized through ValidatorFactory.usingContext(), one injected by a framework, or a test double has no factory to hand over:

// fixed: a MessageInterpolator whose interpolate(…) methods return "customized"
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
    final Validator validator = factory.usingContext().messageInterpolator(fixed).getValidator();
    assertThatBean(new User(null, 0)).usingValidator(validator).isNotValid(violations ->
            assertThat(violations).extracting(ConstraintViolation::getMessage).containsOnly("customized"));
}

Rules that follow from the code

  • usingValidator and usingValidatorFactory are alternatives: each clears the other, and the last call wins.
  • null for either clears it, falling back to the per-call default factory.
  • With a factory, getValidator() is called once per validating call, not at configuration time: configuring takes nothing, and two isValid() calls take two validators.
  • All three methods, and targetingGroups, are final and return the assertion for chaining. The configuration stays on the assertion object across later calls.

Objects handed out after the factory closed

In the default case the factory is closed before the validating method returns, and the consumer of a consumer overload runs after that. So the violation set a consumer receives — and the paths, descriptors and beans reachable from it — were produced by a factory that is already closed by the time the consumer sees them. If a test depends on those objects remaining usable, supply a factory and keep it open.

Not provided

There is no form that takes a Supplier<ValidatorFactory> and closes what it gets. AssertJ has no hook at the end of an assertion chain, so such a factory would be scoped to one call rather than one chain, a shared factory passed through a supplier would be closed by the first call, and closing would invalidate results already handed out. This is recorded as deliberately not done in the repository's _TODO.md.

Clone this wiki locally