Hibernate validator: @Email accepts ask@stackoverflow as valid?
Asked Answered
R

9

64

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?

Rockingham answered 16/12, 2010 at 10:1 Comment(9)
Are you referring to org.hibernate.validator.Email?Naturalism
Also, which version of Hibernate Validator?Naturalism
org.hibernate.validator.constraints.Email; and version 4.0.2.GARockingham
should the question be '@email accepts ask@stackoverflow as valid?' instead of [email protected]Coenesthesia
An email without a dot in the domain is actually valid: isemail.info/aboutBluefish
I got the same problem. Finally, I decided to use my own regular expressions to validate all user inputs. Usinng @Pattern is a complete and a powerfull solution. i.e. for example @Pattern(regexp="\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(.\\w{2,4})+")Prosopopoeia
Please, don't use regexp validation for e-mails, just check if there's an @ inside and try to send an activation e-mail there. The only real validation we need is whether someone interested in our service can click on the attached link or not.Hutner
@ValMartinez Congratulations, your regex rejects valid e-mail addresses with the + symbol in them (very commonly used for address tagging in Gmail and other platforms).Footstool
@chrylis Thanks for your contribution. Yes, you are right. In my system it would be expected as an invalid e-mail and therefore, rejected.Prosopopoeia
S
40

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
                    + ")$";
}
Sperrylite answered 16/12, 2010 at 13:28 Comment(5)
True, but I'm a bit surprised that I will need to make a custom validator for what seems like a really common requirement. I'd expect a flag for this kind of thing.Rockingham
@Satiated solution is much simpler as it only add one line in the annotationsAgley
link in this answer is broken.Redhead
Add to Pattern line message - message="Please provide a valid email address"Betseybetsy
To ignore it have to be ..".+@.+\..+"Betseybetsy
S
62

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 {};
}
Satiated answered 20/9, 2012 at 15:7 Comment(2)
This is indeed the better solutionNadenenader
WARNING: That regex can be used for ReDoS. If you try to insert a long sequence of @ it leads to timeout. This happens because .+ matches @ as well. This one should be safer: [^@]+@[^@]+\.[^@.]+Julius
S
40

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
                    + ")$";
}
Sperrylite answered 16/12, 2010 at 13:28 Comment(5)
True, but I'm a bit surprised that I will need to make a custom validator for what seems like a really common requirement. I'd expect a flag for this kind of thing.Rockingham
@Satiated solution is much simpler as it only add one line in the annotationsAgley
link in this answer is broken.Redhead
Add to Pattern line message - message="Please provide a valid email address"Betseybetsy
To ignore it have to be ..".+@.+\..+"Betseybetsy
S
29

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;
Subservience answered 1/1, 2020 at 19:8 Comment(0)
K
20

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.

Koblenz answered 13/10, 2014 at 19:59 Comment(0)
T
6

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();
    }
}
Tiptoe answered 4/9, 2015 at 21:55 Comment(0)
C
3

If you are going to try the above solution https://mcmap.net/q/300100/-hibernate-validator-email-accepts-ask-stackoverflow-as-valid 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"

Councillor answered 18/7, 2018 at 3:43 Comment(0)
K
2

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.

Kreg answered 3/7, 2017 at 6:28 Comment(3)
... and wrong. "[email protected]" is valid with this regex.Bull
Can you please elaborate little more ?Kreg
Double-Dots in the domain part are not recognized as wrong. Also, umlaut domains and many more valid email address patterns are not detected as valid. Therefore, it is better to let the validation be performed by special libraries, like Apache Commons EmailValidator. See gist.github.com/robertoschwald/ce23c8c23ebd5b93fc3f60c150e35cea how to do that with Hibernate.Bull
A
1

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 {};
}
Assizes answered 20/11, 2018 at 16:47 Comment(0)
P
0

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 { };
Prefrontal answered 25/8, 2014 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.