Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_BLANK, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_EMPTY, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NORMALIZED, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NULL_OR_NOT_BLANK, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SCRIPT_ASSERT, Object.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.UNIQUE_ELEMENTS, Collection.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.URL, CharSequence.class );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public static class HibernateValidatorTypes {
public static final String INN_CHECK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ru.INN";
public static final String KOR_RRN_CHECK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".kor.KorRRN";
public static final String NORMALIZED = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Normalized";
public static final String NULL_OR_NOT_BLANK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NullOrNotBlank";
public static final String UUID = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".UUID";
public static final String NOT_BLANK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NotBlank";
public static final String NOT_EMPTY = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NotEmpty";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hibernate.validator.ap.testmodel.ModelWithJodaTypes;
import org.hibernate.validator.ap.testmodel.ModelWithKorRRNConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithNormalizedConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithNullOrNotBlankConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithUUIDConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithUniqueElementsConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithoutConstraints;
Expand Down Expand Up @@ -742,6 +743,25 @@ public void normalizedConstraints() {
);
}

@Test
@TestForIssue(jiraKey = "HV-2193")
public void nullOrNotBlankConstraints() {
File[] sourceFiles = new File[] {
compilerHelper.getSourceFile( ModelWithNullOrNotBlankConstraints.class )
};

boolean compilationResult =
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, false, true, sourceFiles );

assertFalse( compilationResult );
assertThatDiagnosticsMatch(
diagnostics,
new DiagnosticExpectation( Kind.ERROR, 15 ),
new DiagnosticExpectation( Kind.ERROR, 18 ),
new DiagnosticExpectation( Kind.ERROR, 21 )
);
}

@Test
@TestForIssue(jiraKey = "HV-1867")
public void uuidConstraints() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.ap.testmodel;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.hibernate.validator.constraints.NullOrNotBlank;

public class ModelWithNullOrNotBlankConstraints {

@NullOrNotBlank
public Collection<String> collection;

@NullOrNotBlank
public List<String> list;

@NullOrNotBlank
public Set<String> set;

@NullOrNotBlank
public String string;
}
4 changes: 4 additions & 0 deletions documentation/src/main/asciidoc/reference/_ch02.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,10 @@ The default is `ANY`, which means both IPv4 and IPv6 addresses are considered va
Supported data types::: `CharSequence`
Hibernate metadata impact::: None

`@NullOrNotBlank`:: Validates that the annotated character sequence is either `null` or contains at least one non-whitespace character. This is useful for nullable fields and for use with `Optional`, where an empty `Optional` (extracted as `null`) should be considered valid.
Supported data types::: `CharSequence`
Hibernate metadata impact::: None

`@Range(min=, max=)`:: Checks whether the annotated value lies between (inclusive) the specified minimum and maximum
Supported data types::: `BigDecimal`, `BigInteger`, `CharSequence`, `byte`, `short`, `int`, `long` and the respective wrappers of the primitive types
Hibernate metadata impact::: None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.validator.cfg.defs;

import org.hibernate.validator.cfg.ConstraintDef;
import org.hibernate.validator.constraints.NullOrNotBlank;

/**
* Constraint definition for {@link NullOrNotBlank}.
* @author Koen Aers
* @since 9.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 9.1
* @since 9.2

as 9.1 is already out, we'll add it to the 9.2 🙂

*/
public class NullOrNotBlankDef extends ConstraintDef<NullOrNotBlankDef, NullOrNotBlank> {

public NullOrNotBlankDef() {
super( NullOrNotBlank.class );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import org.hibernate.validator.constraints.NullOrNotBlank.List;

/**
* The annotated element must be {@code null} or not blank. A character sequence is considered
* not blank when it contains at least one non-whitespace character.
* <p>
* This is particularly useful with {@link java.util.Optional} where an empty {@code Optional}
* (extracted as {@code null}) should be valid, but a present value must not be blank.
*
* @author Koen Aers
* @since 9.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 9.1
* @since 9.2

and here 🙂

*/
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NullOrNotBlank {

String message() default "{org.hibernate.validator.constraints.NullOrNotBlank.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };

/**
* Defines several {@code @NullOrNotBlank} annotations on the same element.
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NullOrNotBlank[] value();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.internal.constraintvalidators.hv;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import org.hibernate.validator.constraints.NullOrNotBlank;

/**
* Check that the character sequence is either {@code null} or not blank.
*
* @author Koen Aers
*/
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, CharSequence> {

@Override
public boolean isValid(
CharSequence value,
ConstraintValidatorContext constraintValidatorContext) {
if ( value == null ) {
return true;
}
else {
return !value.toString().trim().isEmpty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ enum BuiltinConstraint {
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD10_CHECK( "org.hibernate.validator.constraints.Mod10Check" ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD11_CHECK( "org.hibernate.validator.constraints.Mod11Check" ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NORMALIZED( "org.hibernate.validator.constraints.Normalized" ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NULL_OR_NOT_BLANK( "org.hibernate.validator.constraints.NullOrNotBlank" ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_EAN( "org.hibernate.validator.constraints.EAN", Arrays.asList( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD10_CHECK ) ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PARAMETER_SCRIPT_ASSERT( "org.hibernate.validator.constraints.ParameterScriptAssert" ),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_RANGE( "org.hibernate.validator.constraints.Range",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD10_CHECK;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD11_CHECK;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NORMALIZED;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NULL_OR_NOT_BLANK;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PARAMETER_SCRIPT_ASSERT;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PL_NIP;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PL_PESEL;
Expand Down Expand Up @@ -114,6 +115,7 @@
import org.hibernate.validator.constraints.Mod10Check;
import org.hibernate.validator.constraints.Mod11Check;
import org.hibernate.validator.constraints.Normalized;
import org.hibernate.validator.constraints.NullOrNotBlank;
import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;
Expand Down Expand Up @@ -335,6 +337,7 @@
import org.hibernate.validator.internal.constraintvalidators.hv.Mod10CheckValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.Mod11CheckValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.NormalizedValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.NullOrNotBlankValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.ParameterScriptAssertValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.ScriptAssertValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.URLValidator;
Expand Down Expand Up @@ -781,6 +784,9 @@ protected Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDes
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NORMALIZED ) ) {
putBuiltinConstraint( tmpConstraints, Normalized.class, NormalizedValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NULL_OR_NOT_BLANK ) ) {
putBuiltinConstraint( tmpConstraints, NullOrNotBlank.class, NullOrNotBlankValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PL_NIP ) ) {
putBuiltinConstraint( tmpConstraints, NIP.class, NIPValidator.class );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ org.hibernate.validator.constraints.LuhnCheck.message = the check
org.hibernate.validator.constraints.Mod10Check.message = the check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod11Check.message = the check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
org.hibernate.validator.constraints.Normalized.message = must be normalized
org.hibernate.validator.constraints.NullOrNotBlank.message = must be null or not blank
org.hibernate.validator.constraints.ParametersScriptAssert.message = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.Range.message = must be between {min} and {max}
org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.test.constraints.annotations.hv;

import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNoViolations;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf;

import java.util.Optional;
import java.util.Set;

import jakarta.validation.ConstraintViolation;

import org.hibernate.validator.constraints.NullOrNotBlank;
import org.hibernate.validator.test.constraints.annotations.AbstractConstrainedTest;
import org.hibernate.validator.testutil.TestForIssue;

import org.testng.annotations.Test;

/**
* @author Koen Aers
*/
@TestForIssue(jiraKey = "HV-2193")
public class NullOrNotBlankConstrainedTest extends AbstractConstrainedTest {

@Test
public void nullIsValid() {
Set<ConstraintViolation<Foo>> violations =
validator.validate( new Foo( null ) );
assertNoViolations( violations );
}

@Test
public void notBlankIsValid() {
Set<ConstraintViolation<Foo>> violations =
validator.validate( new Foo( "foobar" ) );
assertNoViolations( violations );
}

@Test
public void blankIsInvalid() {
Set<ConstraintViolation<Foo>> violations =
validator.validate( new Foo( " " ) );
assertThat( violations ).containsOnlyViolations(
violationOf( NullOrNotBlank.class )
);
}

@Test
public void emptyIsInvalid() {
Set<ConstraintViolation<Foo>> violations =
validator.validate( new Foo( "" ) );
assertThat( violations ).containsOnlyViolations(
violationOf( NullOrNotBlank.class )
);
}

@Test
public void optionalWithValueIsValid() {
Set<ConstraintViolation<Bar>> violations =
validator.validate( new Bar( Optional.of( "foobar" ) ) );
assertNoViolations( violations );
}

@Test
public void emptyOptionalIsValid() {
Set<ConstraintViolation<Bar>> violations =
validator.validate( new Bar( Optional.empty() ) );
assertNoViolations( violations );
}

@Test
public void optionalWithBlankValueIsInvalid() {
Set<ConstraintViolation<Bar>> violations =
validator.validate( new Bar( Optional.of( " " ) ) );
assertThat( violations ).containsOnlyViolations(
violationOf( NullOrNotBlank.class )
);
}

private static class Foo {

@NullOrNotBlank
private final String string;

public Foo(String string) {
this.string = string;
}
}

private static class Bar {

private final Optional<@NullOrNotBlank String> string;

Check warning on line 95 in engine/src/test/java/org/hibernate/validator/test/constraints/annotations/hv/NullOrNotBlankConstrainedTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "string" private field.

See more on https://sonarcloud.io/project/issues?id=hibernate_hibernate-validator&issues=AZznFxcr2FxHgPEkAxNP&open=AZznFxcr2FxHgPEkAxNP&pullRequest=1906

public Bar(Optional<String> string) {
this.string = string;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.hibernate.validator.constraints.Mod10Check;
import org.hibernate.validator.constraints.Mod11Check;
import org.hibernate.validator.constraints.Normalized;
import org.hibernate.validator.constraints.NullOrNotBlank;
import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;
Expand Down Expand Up @@ -156,6 +157,7 @@ public void testMessageProperties() throws NoSuchMethodException, SecurityExcept
violationOf( Mod10Check.class ),
violationOf( Mod11Check.class ),
violationOf( Normalized.class ),
violationOf( NullOrNotBlank.class ),
violationOf( Range.class ),
violationOf( UniqueElements.class ),
violationOf( URL.class ),
Expand Down Expand Up @@ -303,6 +305,9 @@ private static class Bean {
@Normalized(form = java.text.Normalizer.Form.NFKC)
private String normalized = "\uFE64script\uFE65";

@NullOrNotBlank
private String nullOrNotBlank = " ";

@Range(min = 2, max = 4)
private int range = 6;

Expand Down
Loading
Loading