Skip to content

Inspecting Violations

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

Inspecting Violations

These assertions take what validation already produced. They do not extend AbstractValidationAssert: there is nothing left to validate, so they have no usingValidator or targetingGroups.

The examples assume a validator obtained from a ValidatorFactory the test keeps open (see Validator Lifecycle), or the sets handed to a consumer by the validation assertions.

A set of violations

Entry point Returns
assertThatConstraintViolations(Set<? extends ConstraintViolation<T>>) AbstractIterableOfConstraintViolationsAssert<?, T>
assertThatIterableOfConstraintViolations(Iterable<? extends ConstraintViolation<T>>) the same type

The first takes the Set that validate, validateProperty and validateValue return; it delegates to the second.

The assertion is an AssertJ AbstractIterableAssert, so the whole iterable vocabulary is there — hasSize, isEmpty, allSatisfy, extracting, filteredOn, first, singleElement, element(int) and the rest. Its element assertion is the constraint-violation assertion below, and a filtered result is again a violation-set assertion, so navigation stays typed after filteredOn:

final Set<ConstraintViolation<User>> violations = validator.validate(new User(null, -1));

assertThatConstraintViolations(violations)
        .hasSize(2)
        .filteredOn(v -> v.getPropertyPath().toString().equals("name"))
        .hasSize(1)
        .first()
        .hasMessageTemplate("{jakarta.validation.constraints.NotBlank.message}");

assertThatConstraintViolations(violations)
        .isNotEmpty()
        .allSatisfy(v -> assertThat(v.getMessage()).isNotBlank());

There are no violation-specific shortcuts on the set yet, such as "has a violation on name"; those are proposed in #13. Today it is filteredOn / extracting with a lambda.

One violation

assertThatConstraintViolation(ConstraintViolation<T>) returns an AbstractConstraintViolationAssert<?, ConstraintViolation<T>, T>. Each property of ConstraintViolation has an extracting… method returning an assertion for it, and most have a direct has… check.

Property Extract Check
getMessage() — hasMessage(String)
getMessageTemplate() extractingMessageTemplate(), extractingMessageTemplate(AssertFactory) hasMessageTemplate(String)
getRootBean() extractingRootBean(), extractingRootBean(AssertFactory) hasRootBean(T), doesNotHaveRootBean()
getRootBeanClass() extractingRootBeanClass(), extractingRootBeanClass(AssertFactory) hasRootBeanClass(Class<T>)
getLeafBean() extractingLeafBean(), extractingLeafBean(AssertFactory), extractingLeafBean(Function, AssertFactory) hasLeafBean(Object), doesNotHaveLeafBean()
getInvalidValue() extractingInvalidValue(), extractingInvalidValue(Function, AssertFactory) hasInvalidValue(Object)
getPropertyPath() extractingPropertyPath(), extractingPropertyPath(AssertFactory) — see Paths and Nodes
getConstraintDescriptor() extractingConstraintDescriptor(), extractingConstraintDescriptor(Function, AssertFactory) — see Metadata
getExecutableParameters() extractingExecutableParameters(), extractingExecutableParameters(Function, AssertFactory) hasExecutableParameters(Object[]), hasNoExecutableParameters()
getExecutableReturnValue() extractingExecutableReturnValue(), extractingExecutableReturnValue(Function, AssertFactory) hasExecutableReturnValue(Object), hasNoExecutableReturnValue()
unwrap(Class) extractingAsUnwrapped(Class, AssertFactory) isEqualToWhenUnwrappedAs(Class, Object)

hasRootBeanClass rejects a null argument with a NullPointerException. The doesNotHave… / hasNo… methods are the corresponding has… with null.

Message or message template

hasMessage(String) asserts the interpolated message, which depends on the default locale and on any MessageInterpolator. hasMessageTemplate(String) asserts the raw template as declared, which does not:

final ConstraintViolation<User> violation =
        validator.validateValue(User.class, "name", null).iterator().next();

assertThatConstraintViolation(violation)
        .hasMessageTemplate("{jakarta.validation.constraints.NotBlank.message}");

assertThatConstraintViolation(violation)
        .extractingMessageTemplate()
        .startsWith("{")
        .endsWith("}");

Prefer the template unless the interpolated text is what is under test.

Root bean, leaf bean, invalid value

What these hold depends on which Validator method produced the violation. For validateValue there is no instance, so root and leaf bean are null and only the class is known — User_ConstraintViolationAssert_Test checks exactly this:

final var name = "  ";
final ConstraintViolation<User> cv =
        validator.validateValue(User.class, "name", name).iterator().next();

assertThatConstraintViolation(cv)
        .hasNoExecutableParameters()
        .hasNoExecutableReturnValue()
        .hasInvalidValue(name)
        .doesNotHaveLeafBean()
        .doesNotHaveRootBean()
        .hasRootBeanClass(User.class);

assertThatConstraintViolation(cv)
        .extractingConstraintDescriptor()
        .extractingAnnotation()
        .isInstanceOf(NotBlank.class);

For validate(bean) the root bean is the validated instance, so hasRootBean(bean) applies.

From the violation to its path

assertThatConstraintViolation(cv)
        .extractingPropertyPath()
        .hasSize(1)
        .hasNodeSatisfying(0, node -> node
                .hasKind(ElementKind.PROPERTY)
                .hasName("name")
                .doesNotHaveIndex()
                .isNotInIterable());

See Paths and Nodes.

A ConstraintViolationException

assertThatConstraintViolationException(ConstraintViolationException) is for the exception a framework throws when it, rather than the test, triggers validation. Not in 0.6.1.

It extends AssertJ's AbstractThrowableAssert, so hasMessage, hasCause and the rest are available, and adds:

Method
hasViolationOn(String expectedPropertyPath) passes when at least one violation's getPropertyPath().toString() equals the argument
extractingConstraintViolations() a CollectionAssert<ConstraintViolation<?>> of getConstraintViolations()
assertThatConstraintViolationException(exception)
        .hasMessage("invalid user")
        .hasViolationOn("name")
        .hasViolationOn("age");

assertThatConstraintViolationException(exception)
        .extractingConstraintViolations()
        .singleElement()
        .asInstanceOf(constraintViolation())
        .hasMessageTemplate("{jakarta.validation.constraints.NotBlank.message}");

The violations are a CollectionAssert<ConstraintViolation<?>> rather than a violation-set assertion because one exception may carry violations of different root bean types, and the violation-set assertion fixes its root bean type T. To continue with a typed violation assertion, use asInstanceOf(constraintViolation()) as above.

hasViolationOn compares the path's string form, so a method parameter is written as the provider renders it, for example "greet.arg0". On failure it lists the paths the exception does have, sorted, or (none) when getConstraintViolations() is null.

An exception reached as a cause is narrowed with the factory:

assertThat(thrown)
        .cause()
        .asInstanceOf(CONSTRAINT_VIOLATION_EXCEPTION)
        .hasViolationOn("name");

See InstanceOf Factories.

Clone this wiki locally