Hibernate validator: @Email accepts ask@stackoverflow as valid?

HibernateValidationSpring MvcHibernate Validator

Hibernate Problem Overview


I'm using the @Email annotation to validate an e-mail address. The issue I'm having is that it's accepting things like ask@stackoverflow as a valid e-mail address. I guess this is because they want to support intranet addresses, but I can't seem to find a flag so it does check for an extension.

Do I really need to switch to @Pattern (and any recommendations for an e-mail pattern that's flexible) or am I missing something?

Hibernate Solutions


Solution 1 - Hibernate

You can also use constraint composition as a work-around. In the example below, I rely on the @Email validator to do the main validation, and add a @Pattern validator to make sure the address is in the form of [email protected] (I don't recommend using just the @Pattern below for regular Email validation)

@Email(message="Please provide a valid email address")
@Pattern(regexp=".+@.+\\..+", message="Please provide a valid email address")
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface ExtendedEmailValidator {
    String message() default "Please provide a valid email address";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Solution 2 - Hibernate

Actually, @Email from Hibernate Validator uses regexp internally. You can easily define your own constraint based on that regexp, modified as you need (note the + at the end of DOMAIN):

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
@Pattern(regexp = Constants.PATTERN, flags = Pattern.Flag.CASE_INSENSITIVE)
public @interface EmailWithTld {
    String message() default "Wrong email";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

interface Constants {
    static final String ATOM = "[a-z0-9!#$%&'*+/=?^_`{|}~-]";
    static final String DOMAIN = "(" + ATOM + "+(\\." + ATOM + "+)+";
    static final String IP_DOMAIN = "\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]";

    static final String PATTERN =
            "^" + ATOM + "+(\\." + ATOM + "+)*@"
                    + DOMAIN
                    + "|"
                    + IP_DOMAIN
                    + ")$";
}

Solution 3 - Hibernate

While it still possible to implement your own validator or compose a custom one that will aggregate @Email and @Pattern, you don't have to do this anymore!

In one of the recent releases (it's definitely is present in hibernate-validator 6.0.x), @Email has got new regexp attribute that is "an additional regular expression the annotated element must match". In other words, here is a new approach:

@Email(regexp = ".+@.+\\..+")
private String email;

Solution 4 - Hibernate

Actually validating e-mail addresses is really complex. It is not possible to validate that an e-mail address is both syntactically correct and addresses the intended recipient in an annotation. The @Email annotation is a useful minimal check that doesn't suffer from the problem of false negatives.

The next step in validation should be sending an e-mail with a challenge that the user has to complete to establish that the user has access to the e-mail address.

It is better to be accept a few false positives in step 1 and allow some invalid e-mail addresses to pass through than to reject valid users. If you want to apply additional rules you can add more checks, but be really careful about what you assume to be a requirement of a valid e-mail address. For instance there is nothing in the RFCs that dictates that i@nl would be invalid, because nl is a registered country top-level domain.

Solution 5 - Hibernate

Here's a javax.validation email validator using Apache Commons Validator

public class CommonsEmailValidator implements ConstraintValidator<Email, String> {

    private static final boolean ALLOW_LOCAL = false;
    private EmailValidator realValidator = EmailValidator.getInstance(ALLOW_LOCAL);

    @Override
    public void initialize(Email email) {

    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if( s == null ) return true;
        return realValidator.isValid(s);
    }
}

And the annotation:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,  ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {CommonsEmailValidator.class})
@Documented
@ReportAsSingleViolation
public @interface Email {

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

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

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

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        Email[] value();
    }
}

Solution 6 - Hibernate

Obviously I am late to the Party, Still I am replying to this question,

Why cant we use @Pattern annotation with regular expressions in our Validation class like this

public Class Sigunup {

    @NotNull
    @NotEmpty
    @Pattern((regexp="[A-Za-z0-9._%-+]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}")
    private String email;

}

Its easier.

Solution 7 - Hibernate

If you are going to try the above solution https://stackoverflow.com/a/12515543/258544 add the @ReportAsSingleViolation in the annotation defination, this way you will avoid both validation message(one from @Email and one from @Pattern) as it is a composed annotation :

    @Email(message="Please provide a valid email address")
    @Pattern(regexp=".+@.+\\..+", message="Please provide a valid email address")
    @Target( { METHOD, FIELD, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = {})
    @Documented
    @ReportAsSingleViolation

From @interface ReportAsSingleViolation javax.validation:validation-api:1.1.0.Final) annotation definition : "... Evaluation of composed constraints stops on the first validation error in case the composing constraint is annotated with ReportAsSingleViolation"

Solution 8 - Hibernate

You can use Email regexp, also making sure that the validation doesn't fail when the email is empty.

@Email(regexp = ".+@.+\\..+|")
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface ExtendedEmail {

  @OverridesAttribute(constraint = Email.class, name = "message")
  String message() default "{javax.validation.constraints.Email.message}";

  @OverridesAttribute(constraint = Email.class, name = "groups")
  Class<?>[] groups() default {};

  @OverridesAttribute(constraint = Email.class, name = "payload")
  Class<? extends Payload>[] payload() default {};
}

Solution 9 - Hibernate

The constraint composition solution does not work. When Email is used in conjunction with Pattern, the Email regex is held in higher precedence. I believe this is because the Email annotation overrides a few Pattern attributes, namely flags and regexp (the key one here) If I remove @Email, only then will the @Pattern regular expression apply in validations.

/**
 * @return an additional regular expression the annotated string must match. The default is any string ('.*')
 */
@OverridesAttribute(constraint = Pattern.class, name = "regexp") String regexp() default ".*";

/**
 * @return used in combination with {@link #regexp()} in order to specify a regular expression option
 */
@OverridesAttribute(constraint = Pattern.class, name = "flags") Pattern.Flag[] flags() default { };

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjackView Question on Stackoverflow
Solution 1 - HibernateMattView Answer on Stackoverflow
Solution 2 - HibernateaxtavtView Answer on Stackoverflow
Solution 3 - HibernateSlava SemushinView Answer on Stackoverflow
Solution 4 - HibernateDavidView Answer on Stackoverflow
Solution 5 - HibernateNeil McGuiganView Answer on Stackoverflow
Solution 6 - Hibernateuser2245151View Answer on Stackoverflow
Solution 7 - HibernateFrancisco QuiñonesView Answer on Stackoverflow
Solution 8 - HibernateDavithbulView Answer on Stackoverflow
Solution 9 - Hibernateuser1048931View Answer on Stackoverflow