Java email validation with regex checks that a string has the shape of an email address, a local part, one @ sign and a domain. A regex cannot tell us whether the mailbox exists or whether the person typing it owns it. So the useful question is not “which regex is correct” but “how strict should the shape check be before we send a confirmation email”.
We validate email addresses on sign-up forms, in REST request bodies and in CSV imports, so that typos show up before the first mail bounces. The following example tests four email regexes, from a plain @ check to a strict pattern, against the same addresses. The patterns are constants of a small EmailPatterns class that we build up in section 2 to section 5.
boolean atOnly = EmailPatterns.AT_SIGN.matcher("user@example").matches(); // true
boolean needsDot = EmailPatterns.DOT_IN_DOMAIN.matcher("user@example").matches(); // false
boolean html5Dots = EmailPatterns.HTML5.matcher("user..name@example.com").matches(); // true
boolean strictDots = EmailPatterns.STRICT.matcher("user..name@example.com").matches(); // false
Predicate<String> isEmail = EmailPatterns.STRICT.asMatchPredicate();
boolean valid = isEmail.test("user@example.com"); // true
boolean shortTld = isEmail.test("user@example.c"); // false
Notice that the same address “user..name@example.com” passes the browser rule and fails the strict one. We compare the four patterns on 16 sample addresses, explain why a full RFC 5322 regex is a bad fit for a sign-up form, handle internationalized domains with java.net.IDN, and show where a confirmation email replaces any regex.
1. What an Email Address Looks Like
An address has two parts separated by the last @. The part before it is the local part (the mailbox name, such as “user” or “first.last”). The part after it is the domain (such as “example.com”). Each regex in this guide is a different answer to how much we check in each part.
The formal rules come from two standards.
- RFC 5322 section 3.4.1 defines the syntax. It allows far more than most people expect, for example quoted local parts with spaces (“john doe”@example.com) and IP addresses in brackets (user@[192.0.2.1]).
- RFC 5321 section 4.5.3.1 sets the size limits for sending mail, at most 64 characters in the local part and at most 254 characters for the whole address (the 256-character path limit includes two angle brackets).
The HTML Standard adds a third, practical definition. Browsers use it for <input type=”email”>, and section 3 covers it.
2. Two Loose Regexes for a First Check
The loosest useful regex accepts any text that has one @ with something on both sides and no spaces. It catches the typing mistakes that matter most in a form, such as a missing @, an empty local part, or a pasted sentence.
Pattern atSign = Pattern.compile("^[^@\\s]+@[^@\\s]+$");
boolean normal = atSign.matcher("user@example.com").matches(); // true
boolean local = atSign.matcher("user@localhost").matches(); // true
boolean noAt = atSign.matcher("user.example.com").matches(); // false
boolean twoAts = atSign.matcher("user@@example.com").matches(); // false
boolean space = atSign.matcher("user name@example.com").matches(); // false
The class [^@\s] means “any character except @ and whitespace”, and + requires at least one of them. The anchors ^ and $ tie the match to the whole input, and the article on start and end of string explains them in detail.
Most public addresses also have a dot in the domain. Adding \.[^@\s]+ after the domain requires one.
Pattern dotInDomain = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
boolean withDot = dotInDomain.matcher("user@example.com").matches(); // true
boolean noDot = dotInDomain.matcher("user@localhost").matches(); // false
boolean emptyLabel = dotInDomain.matcher("user@example..com").matches(); // true
The last line shows the weak spot. Both loose patterns accept any character in the domain, so “example..com”, “-example.com” and “[192.0.2.1]” all pass. That is acceptable when a confirmation email follows, because the mail server rejects those addresses anyway.
3. The HTML5 Regex Used by Browsers
The HTML Standard defines a “valid email address” and publishes a regex for it. Browsers apply this rule to <input type=”email”>. The specification calls it “a willful violation of RFC 5322” because the RFC is too strict in some places and too loose in others for a web form. Using the same pattern on the server means the browser and the back end agree.
Pattern html5 = Pattern.compile(
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+"
+ "@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
+ "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
boolean apostrophe = html5.matcher("o'neil@example.org").matches(); // true
boolean noDot = html5.matcher("user@localhost").matches(); // true
boolean leadingDot = html5.matcher(".user@example.com").matches(); // true
boolean hyphenStart = html5.matcher("user@-example.com").matches(); // false
boolean emptyLabel = html5.matcher("user@example..com").matches(); // false
The pattern has three pieces.
| Part | Meaning |
|---|---|
| [a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+ | Local part, with letters, digits, dots and the symbols RFC 5322 calls atext |
| [a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? | One domain label of 1 to 63 characters that starts and ends with a letter or digit, with hyphens only inside |
| (?:\.label)* | Zero or more further labels, each after a dot |
The HTML5 regex accepts “user@localhost” because the domain needs no dot. It also accepts dots anywhere in the local part, including “.user” and “user..name”. It rejects a domain that starts with a hyphen or contains an empty label.
4. A Strict Regex With Dot Rules and Length Limits
A stricter pattern fixes the gaps of the HTML5 one that matter for public addresses. It keeps the same allowed characters and adds three rules.
- The local part cannot start or end with a dot, and two dots cannot follow each other.
- The domain needs at least one dot, and the last label (the top-level domain, such as “com”) has two or more letters.
- The local part has at most 64 characters and the whole address at most 254.
Pattern strict = Pattern.compile(
"^(?=.{1,254}$)(?=.{1,64}@)"
+ "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*"
+ "@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$");
boolean ukDomain = strict.matcher("first.last@example.co.uk").matches(); // true
boolean plusTag = strict.matcher("user+news@example.com").matches(); // true
boolean oneLetterTld = strict.matcher("user@example.c").matches(); // false
boolean doubleDot = strict.matcher("user..name@example.com").matches(); // false
boolean longLocal = strict.matcher("a".repeat(65) + "@example.com").matches(); // false, local part over 64
The new parts of the strict pattern each add one rule.
| Part | Meaning |
|---|---|
| (?=.{1,254}$) | Lookahead, the whole input is 1 to 254 characters long |
| (?=.{1,64}@) | Lookahead, 1 to 64 characters come before the @ |
| atom(?:\.atom)* | Local part, groups of allowed characters joined by single dots |
| (?:label\.)+ | One or more domain labels, each followed by a dot |
| [a-zA-Z]{2,} | Top-level domain of two or more letters |
A lookahead, written (?=…), checks a condition at the current position without consuming any characters. Here both lookaheads sit at the start, so the main pattern still begins at the first character. The regex min and max length article explains this technique on its own.
Strict also means that some real addresses get rejected. An address on a single-label domain or with a quoted local part is valid by the RFC but fails here. For a public sign-up form that trade-off is fine, while an internal tool that sends to “admin@localhost” needs the HTML5 regex.
5. Which Addresses Each Regex Accepts
For reuse, we keep the four patterns as compiled constants in one class. A Pattern is immutable and thread-safe, so one instance serves every request.
final class EmailPatterns {
static final Pattern AT_SIGN = Pattern.compile("^[^@\\s]+@[^@\\s]+$");
static final Pattern DOT_IN_DOMAIN = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
static final Pattern HTML5 = Pattern.compile(
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+"
+ "@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
+ "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
static final Pattern STRICT = Pattern.compile(
"^(?=.{1,254}$)(?=.{1,64}@)"
+ "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*"
+ "@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$");
}
Running all four patterns over the same inputs shows the differences at a glance. The first four rows are ordinary valid addresses, and the rest are either invalid or rare forms that only the RFC allows.

| Input | AT_SIGN | DOT_IN_DOMAIN | HTML5 | STRICT |
|---|---|---|---|---|
| user@example.com | true | true | true | true |
| first.last@example.co.uk | true | true | true | true |
| user+news@example.com | true | true | true | true |
| o’neil@example.org | true | true | true | true |
| user@localhost | true | false | true | false |
| user@example.c | true | true | true | false |
| .user@example.com | true | true | true | false |
| user..name@example.com | true | true | true | false |
| user@-example.com | true | true | false | false |
| user@example..com | true | true | false | false |
| user@[192.0.2.1] | true | true | false | false |
| “john doe”@example.com | false | false | false | false |
| user@@example.com | false | false | false | false |
| user name@example.com | false | false | false | false |
| user.example.com | false | false | false | false |
| @example.com | false | false | false | false |
The row “user+news@example.com” deserves attention. The plus sign is used for sub-addressing (a tag that the mail server ignores), and many hand-written regexes reject it by mistake. All four patterns here accept it.
6. Why a Full RFC 5322 Regex Is a Bad Idea
Regexes that claim full RFC 5322 support are common online. They run to several hundred characters, and versions that also handle comments and folded whitespace are much longer. We do not recommend them, for three reasons.
- They accept addresses that our mail server will reject anyway. The RFC allows quoted strings, comments, IP literals and characters such as backticks and quotes, whereas real mail servers accept a much smaller format, so a technically valid address is useless if we cannot send to it.
- Nobody on the team can review them. A bug in a 400-character pattern stays hidden until a user reports it.
- Large patterns with nested quantifiers can be slow on bad input. Some inputs make the regex engine try a huge number of paths, a problem known as ReDoS (regular expression denial of service). The four patterns in this guide have no ambiguous nested repetition.
Even a perfect RFC regex answers only “is this a possible address”, not “does this mailbox exist”.
7. Normalizing Input and Handling Unicode Domains
Users paste addresses with leading spaces and type domains in uppercase. Domain names are not case-sensitive, so lowercasing the domain is safe. The local part is case-sensitive by RFC 5321, so we keep its case as typed, even though most providers ignore it.
Domains can also contain non-ASCII letters, such as “caf\u00e9.example” with an e-acute. DNS stores these in an ASCII form called punycode, and Java converts between the two forms with java.net.IDN. Converting the domain first lets the ASCII-only regex accept such addresses.
final class Emails {
static Optional<String> normalize(String input) {
if (input == null) {
return Optional.empty();
}
String trimmed = input.strip();
int at = trimmed.lastIndexOf('@');
if (at < 1 || at == trimmed.length() - 1) {
return Optional.empty();
}
try {
String domain = IDN.toASCII(trimmed.substring(at + 1)).toLowerCase(Locale.ROOT);
return Optional.of(trimmed.substring(0, at) + "@" + domain);
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
static boolean isValidEmail(String input) {
return normalize(input).filter(EmailPatterns.STRICT.asMatchPredicate()).isPresent();
}
}
The method normalize() strips whitespace, splits at the last @, runs IDN.toASCII() on the domain and lowercases it. The method IDN.toASCII() throws an IllegalArgumentException for a domain it cannot convert, so we return an empty Optional instead of letting a bad form value crash the request.
String punycode = IDN.toASCII("caf\u00e9.example"); // "xn--caf-dma.example"
boolean rawUnicode = EmailPatterns.STRICT.matcher("user@caf\u00e9.example").matches(); // false
Optional<String> cleaned = Emails.normalize(" User@Example.COM "); // Optional[User@example.com]
boolean unicodeDomain = Emails.isValidEmail("user@caf\u00e9.example"); // true
boolean missing = Emails.isValidEmail(null); // false
Non-ASCII characters in the local part are a separate feature called SMTPUTF8 (RFC 6531), which the mail server must support. We add \p{L} to the local-part classes only if it does.
8. When to Send a Confirmation Email Instead
A regex cannot catch “user@gmial.com” or an address that belongs to someone else. Only one check proves that an address works and belongs to the person typing it. We send a message with a link or code, and mark the address as verified when the person uses it. The token in that link must be random, single-use and valid for a few hours only.
A newsletter sign-up for a cooking site shows a practical split between the checks.
- In the form, use a loose regex (DOT_IN_DOMAIN or HTML5) so that typing mistakes show up at once and no real user is blocked.
- Before storing, normalize the address and apply STRICT if the application sends mail over the public internet.
- For accounts, password resets and anything where the address identifies a person, send a confirmation email and do not trust the address until it is confirmed.
Libraries are another option. Apache Commons Validator has an EmailValidator class that also checks the top-level domain against a list, and Jakarta Bean Validation offers the @Email annotation for request objects. Both still check only the format.
9. Running the Email Validation Project
The example project on GitHub prints every sample against all four patterns, which is where the table in section 5 comes from. The JUnit 6 test class asserts each cell of that table plus the length limits, normalization and IDN conversion. Building it requires Java 25 and Maven 3.9 or newer.
mvn -q compile exec:java
mvn test
10. Email Regex FAQs
Developers ask these follow-up questions most often when they search for email validation in Java.
10.1. Does Java Have a Built-in Email Validator?
No, the JDK has no email validation class. The common choices are a regex such as STRICT, EmailValidator from Apache Commons Validator, the @Email constraint of Jakarta Bean Validation (in Spring Boot through spring-boot-starter-validation), or InternetAddress.validate() from Jakarta Mail. All of them check the format only.
10.2. Is a Plus Sign Valid in an Email Address?
Yes. RFC 5322 allows “+” in the local part, and providers such as Gmail and Microsoft 365 use it for sub-addressing, as in “user+news@example.com”. A regex that rejects it blocks real users, so all four patterns in this article accept it.
10.3. Should the Regex Limit the Top-Level Domain to 2 to 6 Letters?
No. Many current top-level domains are longer, such as “.photography” or “.international”, and a rule like [a-zA-Z]{2,6} rejects valid addresses on them. The STRICT pattern uses {2,} with no upper limit.
boolean longTld = EmailPatterns.STRICT.matcher("chef@example.photography").matches(); // true
10.4. What Is the Maximum Length of an Email Address?
254 characters for the whole address and 64 for the local part. RFC 5321 limits the forward path to 256 characters, and that limit includes the two angle brackets around the address. The STRICT pattern enforces both numbers with two lookaheads.
10.5. Can a Regex Check That an Email Address Exists?
No. A regex sees only the text. Only a delivered confirmation email, with a link or code that the person uses, proves that the mailbox exists and belongs to the person who typed it.
11. Conclusion
A regex for email in Java should check the shape of the address and nothing more. The HTML5 regex matches what browsers accept, and the strict regex adds dot rules, a letter-only top-level domain and the 64 and 254 character length limits.
Full RFC 5322 patterns cost more than they give. Whichever pattern we choose, the confirmation email is the check that proves the address is real.
12. References
- RFC 5322 Internet Message Format, section 3.4.1
- RFC 5321 Simple Mail Transfer Protocol, section 4.5.3.1
- HTML Standard, valid email address
- OWASP Input Validation Cheat Sheet
- Pattern (Java SE 25 API)
- IDN (Java SE 25 API)
Happy Learning !!
Good post, but mail reg ex are really complex:
In my case example@mailcom passes as true…
True. These regex are not easy to understand and maintain. Perhaps, for this reason, double optins were invented.
Great Content, just another example of a invalid email, “example.@yahoo.com”, this invalid address hasn’t been handled by any of the above Regex. Can you please help me with this one?
good pattern but in my case ‘example@-domain.com’ passed, so I added this bit after ‘@’: (?!-)
"^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?!-)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$"This example was clear and easy to understand ,Thanks for sharing.
package rashid.jorvee; public class ValidEmailValidator { public static void main(String[] args) { String[] emailAddresses= {&quot;rasj@hds&quot;,&quot;adyu+@sud.com&quot;,&quot;asg_sj-@asgd.co&quot;,&quot;rashid.ta@rasj.com&quot;,&quot;fdh%sdfkj_@dsj.cojn&quot;}; for(String email : emailAddresses) { if(validEmail(email)) { System.out.println(&quot;Valid email address :&quot; +email); } else { System.out.println(&quot;Invalid email address :&quot;+email); } } } static boolean validEmail(String email) { return email.matches(&quot;[a-zA-Z0-9._-][a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}&quot;); } }Found this and it looked promising but the following email address is not validated, why not?
name.name@name-names.domain.no
This is a valid address… What am I missing?
String EMAIL_PATTERN = " ^[\\w!#$%&’*+/=?`{|}~^-]+(?:\\.[\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$"; Pattern pattern; Matcher matcher; matcher = pattern.matcher(o.toString()); if(!matcher.matches()) { throw new ValidatorException(""); }The way each example builds upon the prior one was very helpful in understanding the final example. The inclusion of the sample emails for testing made my testing that much easier. Thank you for this.
I think it’s not limited to 6 chars in the TLD anymore.
How we can only check @ and . and allow all other characters
Probably you looked up only at first example (there are multiple examples if you scroll down). I have clearly mentioned the rule (only checking @ symbol).. for simplicity purpose. Valid formats are given after that and their rules as well. Try them.
No doubt, these are the perfect examples according to the RFCs.
Regex allowing email addresses permitted by RFC 5322 – this is complete and hard to find anywhere else.
Good explanation.
Thank you