Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →To create a custom Bean Validation 2.0 constraint, define a runtime-retained annotation marked with @Constraint, connect it to a ConstraintValidator, then apply the annotation to a supported program element and validate it with a provider. Use a value-level constraint for one value and a class-level constraint when a rule compares multiple properties. Decide explicitly how nulls should behave.
How a custom constraint works
A custom constraint has two linked parts: the annotation that declares the rule and one or more validator classes that evaluate it. The annotation’s validatedBy member names the implementation. As the Jakarta Bean Validation specification puts it, “The constraint validation implementation performs the validation of a given constraint annotation for a given type.” Its implementation contract is ConstraintValidator.
Bean Validation 2.0 is the final specification dated August 5, 2019. It uses Java 8 language features and defines an object-level constraint declaration and validation facility for Java application developers. Read the Bean Validation 2.0 specification.
Define the constraint annotation
The annotation declares where the rule can be used, which validator implements it, and the standard metadata expected of a constraint. This example configures a maximum length:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = MaxCodeLengthValidator.class)
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface MaxCodeLength {
String message() default "{com.example.MaxCodeLength.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int max();
}
@Constraint(validatedBy = ...)connects the annotation to its validator.@Targetlimits placement to locations the validator supports. Add targets such asMETHOD,TYPE, orTYPE_USEonly when the implementation is appropriate for them.@Retention(RUNTIME)keeps the annotation available to the validation provider at runtime.message,groups, andpayloadare the standard constraint members. Add domain members such asmaxonly when callers need to configure the rule.
The imports above use the javax.validation namespace associated with Bean Validation 2.0. Later Jakarta Validation releases use a different namespace; do not mix imports from different API generations in one application.
Implement the validator
Implement ConstraintValidator<AnnotationType, ValueType>. The provider calls initialize() with the annotation instance, so its configuration is available to the validator. The following validator accepts strings no longer than the configured maximum and deliberately treats null as valid:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MaxCodeLengthValidator
implements ConstraintValidator<MaxCodeLength, String> {
private int max;
@Override
public void initialize(MaxCodeLength constraint) {
this.max = constraint.max();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || value.length() <= max;
}
}
Pair this constraint with @NotNull when the value is required. This separation keeps the custom rule focused on length; if null itself is what the custom rule is meant to reject, implement and document that behavior instead. The validator’s type parameter should resolve to a non-parameterized type or use unbounded wildcards. Keep it as specific as the rule permits so the provider can resolve the right validator unambiguously. If an annotation supports several value types, provide separate validator implementations and confirm the provider’s resolution rules.
Rank #2
Choose the correct validation target
The annotation target and validator implementation must both support the place where the constraint is applied. Choose the narrowest target that matches the rule:
| Target | Suitable rule | Implementation consideration |
|---|---|---|
| Field or getter/property | A rule about one value, such as an allowed code or normalized identifier | Use a validator whose value type matches the annotated property. |
| Class/type | A rule comparing two or more properties, such as start and end dates | Validate the bean type and optionally attach a violation to a property path. |
| Method or constructor parameter/return value | An executable contract at a service or endpoint boundary | Include the relevant executable element in @Target and support the value type or executable validation target. |
| Cross-parameter | A rule involving the complete parameter array of a method or constructor | Mark the validator with the specification’s required validation target for cross-parameter validation. |
| Container element | A rule about values inside a generic container such as List, Map, or Optional |
Use a type-use target and a validator appropriate to the contained value. Container-element constraints are a Bean Validation 2.0 feature. |
Do not put a constraint on every possible target by default. A broad @Target is useful only if the validator can correctly handle each location. The specification sets out the supported targets and the matching annotation and validator requirements in its constraint-definition rules.
Write a class-level constraint for cross-property rules
When validity depends on multiple properties, put the constraint on the bean type and validate that bean, rather than trying to make a field validator inspect unrelated fields. For example, a date range can require the end date not to precede the start date:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = ValidRangeValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
public @interface ValidRange {
String message() default "{com.example.ValidRange.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ValidRangeValidator
implements ConstraintValidator<ValidRange, Booking> {
@Override
public boolean isValid(Booking booking, ConstraintValidatorContext context) {
if (booking == null
|| booking.getStartDate() == null
|| booking.getEndDate() == null) {
return true;
}
return !booking.getEndDate().isBefore(booking.getStartDate());
}
}
This example leaves null handling to separate constraints, such as @NotNull on the bean and date properties. The bean-null check is also intentional: the range validator does not claim that a missing object or date is a range-order violation.
A class-level violation normally identifies the object rather than a particular input field. If a form or API needs field-specific feedback, disable the default violation and build one against a property path:
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("endDate")
.addConstraintViolation();
return false;
Choose the property name and message deliberately: clients may depend on stable field paths or message keys. Keep changing display text in a provider message bundle rather than embedding it in validator logic. For the value-level example, a properties bundle entry could be com.example.MaxCodeLength.message=must be no longer than {max} characters; message interpolation can use annotation attributes such as {max}.
Rank #4
Apply and validate the constraint
Once the annotation and validator are defined, apply them to a matching target. For example:
public class UserInput {
@MaxCodeLength(max = 20)
private String code;
// constructor, getter and other application code
}
Run validation through a Bean Validation provider. Hibernate Validator is the reference implementation and a commonly used provider; the standard annotation and validator contract is specified by Bean Validation, while provider-specific extensions should be treated separately. The official project describes custom constraints as a way to capture application-specific semantics. See the Hibernate Validator project.
For XML overrides, metadata APIs, and framework integration guidance, consult the Hibernate Validator 6.0 reference guide. These are Hibernate Validator resources; their extensions and integration details are not automatically guarantees of the Bean Validation specification.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteBest Value
Test the constraint behavior
Tests should cover the rule and its contract, not just one passing and one failing value. Include:
- a value that should pass and one that should fail;
- null input, including the interaction with
@NotNullif null is delegated; - each configured annotation parameter, including boundary values such as exactly the maximum;
- message interpolation from the configured bundle;
- placement on every intended target, including a class-level violation path or executable target where applicable;
- multiple supported types, if the annotation declares more than one validator.
Bean Validation 2.0 annotations are repeatable in Java 8. When a constraint should appear more than once on the same element, repeated annotations are preferred over the older nested @List container idiom. Keep separate declarations when they express distinct configurations, such as different bounds.
When Hibernate Validator-specific behavior matters
Hibernate Validator implements Bean Validation and provides additional documentation for areas such as XML constraint mapping, metadata, and framework integration. Keep portability in view: code using the specification’s @Constraint, ConstraintValidator, and supported targets is the portable foundation; an extension or integration feature documented by Hibernate Validator should be identified as provider-specific before relying on it.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

