Java Expressões Regulares
Uma expressão regular (em inglês regular expression, apelidada de regex) é um texto que descreve um formato: "cinco dígitos, um hífen e mais três dígitos" é o formato de um CEP. Com ela, o Java responde perguntas como "este texto tem cara de CEP?", "onde estão os números nesta frase?" e "troque todos os espaços repetidos por um só".
- Os blocos de uma regex:
\d,[A-Z],+,{5},( )… - Por que no Java se escreve
"\\d+", com duas barras. String.matches,replaceAll,replaceFirstesplitcom regex.PatterneMatcher:findpara achar vários pedaços, grupos para separar as partes.- Validar o formato de CEP, CPF e e-mail, e entender a
PatternSyntaxException.
Você já conhece os métodos de String e os caracteres especiais como \n; esta aula usa os dois. Os exemplos rodaram no JDK 25 da máquina da Ana, na pasta ~/java.
A ideia antes do código
Quando a Ana olha para 01310-100, ela reconhece um CEP sem pensar: "são 5 números, um tracinho, 3 números". Se alguém escrever 1310-100, ela estranha: falta um número. Ela não decorou todos os CEPs do Brasil; ela conhece o molde.
Uma regex é esse molde escrito numa linguagem que o computador entende. O molde do CEP fica assim:
- \d
- Um dígito (de digit): qualquer caractere de
0a9. - {5}
- O item anterior, exatamente 5 vezes.
- -
- Um hífen de verdade. Letras e símbolos comuns representam eles mesmos.
- \d{3}
- Mais três dígitos.
Juntando: \d{5}-\d{3}. A regex é uma linguagem pequena e própria, que existe também no grep do Linux, no JavaScript, no Python… O que você aprender aqui vale em quase todo lugar.
1. A primeira regex: String.matches
O jeito mais rápido de usar uma regex é o método matches ("casa com") de qualquer String. Ele devolve true se o texto inteiro se encaixa no molde:
public class Main {
public static void main(String[] args) {
String cep = "01310-100";
System.out.println(cep.matches("\\d{5}-\\d{3}"));
System.out.println("1310-100".matches("\\d{5}-\\d{3}"));
System.out.println("01310100".matches("\\d{5}-\\d{3}"));
System.out.println("01310-100".matches("\\d{5}-?\\d{3}"));
System.out.println("01310100".matches("\\d{5}-?\\d{3}"));
}
}
$ javac Main.java && java Main true false false true true
| Linha | Resultado | Por quê |
|---|---|---|
"01310-100" com \d{5}-\d{3} | true | 5 dígitos, hífen, 3 dígitos. |
"1310-100" | false | Só 4 dígitos antes do hífen. |
"01310100" | false | Falta o hífen. |
"01310-100" com \d{5}-?\d{3} | true | O ? torna o hífen opcional (zero ou uma vez). |
"01310100" com \d{5}-?\d{3} | true | Sem hífen também vale agora. |
Por que "\\d" com duas barras?
Repare que no código está escrito "\\d{5}-\\d{3}", e não \d{5}-\d{3}. Acontece que há duas linguagens aqui, uma dentro da outra:
- O Java lê o texto entre aspas primeiro. Para o Java, a barra invertida começa um caractere especial (
\né quebra de linha,\"é aspas). Para ter uma barra de verdade dentro de umString, escreve-se\\. - Depois a regex recebe o resultado:
\d{5}-\d{3}, com uma barra só, que é o que ela queria.
Regra prática: toda barra da regex vira duas barras no código Java. \d → "\\d"; \s → "\\s"; \. → "\\.". Se você esquecer, o javac reclama:
public class Main {
public static void main(String[] args) {
System.out.println("12345".matches("\d+"));
}
}
$ javac Main.java
Main.java:3: error: illegal escape character
System.out.println("12345".matches("\d+"));
^
1 error
"Caractere de escape ilegal": o Java não conhece nenhum \d (ele conhece \n, \t, \"…). A setinha aponta para o d. Conserto: "\\d+".
Missão: no JShell, teste se "01310-100" casa com o molde de CEP. Digite "01310-100".matches("\\d{5}-\\d{3}") e veja o true. Depois experimente tirar um dígito.
"01310-100".matches("\\d{5}-\\d{3}")
2. Os blocos de uma regex
Toda regex é montada com poucas peças. Não precisa decorar: volte a esta tabela quando precisar. A coluna "no Java" mostra como escrever entre aspas.
| Regex | No Java | Casa com | Exemplo que casa |
|---|---|---|---|
abc | "abc" | Exatamente esses caracteres | abc |
. | "." | Qualquer caractere (um) | a, 7, - |
\d | "\\d" | Um dígito 0–9 | 5 |
\D | "\\D" | Um caractere que não é dígito | (, a |
\s | "\\s" | Um espaço, tab ou quebra de linha | |
\w | "\\w" | Letra sem acento, dígito ou _ | a, Z, 3, _ |
[aeiou] | "[aeiou]" | Um dos caracteres da lista | e |
[A-Z] | "[A-Z]" | Um caractere da faixa (maiúsculas) | Q |
[^0-9] | "[^0-9]" | Um caractere fora da lista | x |
\. | "\\." | Um ponto de verdade | . |
x? | "x?" | x zero ou uma vez (opcional) | (nada), x |
x* | "x*" | x zero ou mais vezes | (nada), xxx |
x+ | "x+" | x uma ou mais vezes | x, xxxx |
x{3} | "x{3}" | Exatamente 3 vezes | xxx |
x{2,4} | "x{2,4}" | De 2 a 4 vezes | xx, xxxx |
a|b | "a|b" | a ou b | b |
( ) | "(…)" | Agrupa e "guarda" o pedaço (grupo) | veja a seção 5 |
^ e $ | "^…$" | Começo e fim do texto | (úteis com find) |
\w não aceita acentos
No Java, \w é só [a-zA-Z_0-9]. Por isso "maçã".matches("\\w+") dá false (o ç e o ã ficam de fora). Para "qualquer letra, de qualquer idioma", use \p{L}: "maçã".matches("\\p{L}+") dá true. Conferido no JShell do laboratório:
$ jshell
jshell> "maçã".matches("\\w+")
$1 ==> false
jshell> "maçã".matches("\\p{L}+")
$2 ==> true
Maiúsculas e minúsculas
Uma regex diferencia maiúsculas de minúsculas: "JAVA".matches("java") é false. Para ignorar a diferença, comece a regex com (?i) (de ignore case): "JAVA".matches("(?i)java") é true.
Missão: a placa Mercosul tem 3 letras maiúsculas, 1 dígito, 1 letra maiúscula e 2 dígitos (ABC1D23). Escreva a regex em padrao para que só ABC1D23 e BRA2E19 deem true. Lembre-se das barras duplas.
public class Main {
public static void main(String[] args) {
String padrao = ""; // escreva a regex aqui
String[] placas = { "ABC1D23", "ABC1234", "abc1d23", "BRA2E19" };
for (String p : placas) {
System.out.println(p + " -> " + p.matches(padrao));
}
}
}
public class Main {
public static void main(String[] args) {
String padrao = "[A-Z]{3}\\d[A-Z]\\d{2}";
String[] placas = { "ABC1D23", "ABC1234", "abc1d23", "BRA2E19" };
for (String p : placas) {
System.out.println(p + " -> " + p.matches(padrao));
}
}
}
3. Trocar e cortar: replaceAll e split
Alguns métodos de String recebem uma regex no lugar de um texto fixo:
public class Main {
public static void main(String[] args) {
String bagunca = "Ana gosta de Java";
System.out.println(bagunca.replaceAll("\\s+", " "));
String iso = "2026-10-16";
System.out.println(iso.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1"));
String telefone = "(11) 98765-4321";
System.out.println(telefone.replaceAll("\\D", ""));
System.out.println("banana".replaceFirst("a", "A"));
System.out.println("banana".replace("a", "A"));
}
}
$ javac Main.java && java Main Ana gosta de Java 16/10/2026 11987654321 bAnana bAnAnA
| Chamada | O que fez |
|---|---|
replaceAll("\\s+", " ") | Cada sequência de um ou mais espaços virou um espaço só. |
replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1") | Os parênteses guardam ano, mês e dia; na troca, $1, $2 e $3 são esses pedaços, em outra ordem. |
replaceAll("\\D", "") | Apagou tudo o que não é dígito: sobrou só o número. |
replaceFirst("a", "A") | Trocou só a primeira ocorrência. |
replace("a", "A") | Este não usa regex: troca o texto literal, todas as vezes. |
replace ou replaceAll?
Se você quer trocar um texto fixo, use replace: ele não interpreta nada. Com replaceAll, caracteres como $, . e * têm significado especial. Por exemplo, "preço: R$ 10".replaceAll("R$", "BRL") devolve o texto sem mudança ("preço: R$ 10"), porque na regex $ quer dizer "fim do texto", e não há um R no fim.
split: cortar por um molde
O split ("dividir") corta o texto em pedaços onde a regex casar, e devolve um array:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String feira = "maçã, banana;uva kiwi";
String[] frutas = feira.split("[,;\\s]+");
System.out.println(Arrays.toString(frutas));
System.out.println(frutas.length);
String versao = "25.0.4";
System.out.println(Arrays.toString(versao.split(".")));
System.out.println(Arrays.toString(versao.split("\\.")));
}
}
$ javac Main.java && java Main [maçã, banana, uva, kiwi] 4 [] [25, 0, 4]
A regex [,;\s]+ quer dizer "um ou mais caracteres que sejam vírgula, ponto e vírgula ou espaço". Assim, a lista bagunçada virou quatro frutas limpas. Já a linha seguinte é uma armadilha famosa: split(".") devolveu um array vazio. Na regex, o ponto quer dizer "qualquer caractere": todos os caracteres viraram separadores, e o que sobrou foram pedaços vazios, que o split descarta. Para cortar no ponto de verdade, escape-o: split("\\.").
4. Pattern e Matcher: achar pedaços no meio do texto
O matches responde só "sim ou não, para o texto todo". Para procurar pedaços dentro de um texto maior, o Java tem duas classes do pacote java.util.regex:
Pattern("padrão"): a regex já compilada, isto é, conferida e preparada para uso. Você cria comPattern.compile("…").Matcher("casador"): um "leitor" que percorre um texto procurando o padrão. Você cria compadrao.matcher(texto).
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String frase = "Ana comprou 3 maçãs, 12 bananas e pagou 25 reais.";
Pattern padrao = Pattern.compile("\\d+");
Matcher m = padrao.matcher(frase);
while (m.find()) {
System.out.println("Achei " + m.group() + " nas posições " + m.start() + " a " + m.end());
}
}
}
$ javac Main.java && java Main Achei 3 nas posições 12 a 13 Achei 12 nas posições 21 a 23 Achei 25 nas posições 40 a 42
| Linha | O que faz |
|---|---|
import java.util.regex.… | Traz as duas classes (veja pacotes e import). |
Pattern.compile("\\d+") | Prepara a regex "um ou mais dígitos". |
padrao.matcher(frase) | Cria o leitor que vai percorrer a frase. |
while (m.find()) | find ("encontrar") procura o próximo pedaço que casa. Devolve true se achou e false quando acabou o texto; por isso cabe num while. |
m.group() | O pedaço que acabou de ser encontrado. |
m.start(), m.end() | Onde ele começa e onde termina (a posição logo depois do último caractere), contando do zero, como no substring. |
matches() × find()
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("\\d+");
Matcher m1 = p.matcher("Pedido 42");
System.out.println("matches: " + m1.matches());
Matcher m2 = p.matcher("Pedido 42");
System.out.println("find: " + m2.find() + " -> " + m2.group());
System.out.println("matches em \"42\": " + p.matcher("42").matches());
}
}
$ javac Main.java && java Main matches: false find: true -> 42 matches em "42": true
matches exige que o texto inteiro seja só dígitos; "Pedido 42" tem letras, então é false. find aceita o pedaço 42 no meio. Use matches para validar e find para procurar.
5. Grupos: separar as partes
Os parênteses de uma regex criam grupos: além de casar, o Java guarda o pedaço que caiu em cada par de parênteses. Eles são numerados da esquerda para a direita, a partir de 1; o grupo 0 é o trecho inteiro. Você também pode dar nomes com (?<nome>…):
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Pattern data = Pattern.compile("(\\d{2})/(\\d{2})/(\\d{4})");
Matcher m = data.matcher("16/10/2026");
if (m.matches()) {
System.out.println("Tudo: " + m.group(0));
System.out.println("Dia: " + m.group(1));
System.out.println("Mês: " + m.group(2));
System.out.println("Ano: " + m.group(3));
}
Pattern comNomes = Pattern.compile("(?<dia>\\d{2})/(?<mes>\\d{2})/(?<ano>\\d{4})");
Matcher m2 = comNomes.matcher("Entrega em 25/12/2026, sem falta.");
if (m2.find()) {
System.out.println(m2.group("ano") + "-" + m2.group("mes") + "-" + m2.group("dia"));
}
}
}
$ javac Main.java && java Main Tudo: 16/10/2026 Dia: 16 Mês: 10 Ano: 2026 2026-12-25
Repare que group(1) devolve um String. Para fazer contas, converta com Integer.parseInt (veja wrappers). Para datas de verdade, o Java tem uma ferramenta melhor: java.time. A regex é útil para achar a data no meio de um texto.
6. Validar formatos: CEP, CPF e e-mail
Um uso clássico é conferir se o que o usuário digitou tem o formato certo antes de seguir adiante:
public class Main {
public static void main(String[] args) {
String[] cpfs = { "123.456.789-09", "12345678909", "123.456.789-0" };
for (String c : cpfs) {
System.out.println(c + " -> " + c.matches("\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}"));
}
String[] ceps = { "01310-100", "0131-0100" };
for (String c : ceps) {
System.out.println(c + " -> " + c.matches("\\d{5}-\\d{3}"));
}
String[] emails = { "ana@email.com", "ana.souza@loja.com.br", "ana@", "ana email.com" };
for (String e : emails) {
System.out.println(e + " -> " + e.matches("[\\w.+-]+@[\\w-]+(\\.[\\w-]+)+"));
}
}
}
$ javac Main.java && java Main 123.456.789-09 -> true 12345678909 -> false 123.456.789-0 -> false 01310-100 -> true 0131-0100 -> false ana@email.com -> true ana.souza@loja.com.br -> true ana@ -> false ana email.com -> false
| Formato | Regex (como fica no Java) | Leitura |
|---|---|---|
| CEP | "\\d{5}-\\d{3}" | 5 dígitos, hífen, 3 dígitos |
| CPF com pontuação | "\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}" | 3 dígitos, ponto, 3, ponto, 3, hífen, 2 (o ponto escapado é um ponto de verdade) |
| E-mail simples | "[\\w.+-]+@[\\w-]+(\\.[\\w-]+)+" | Letras/dígitos/. + -, arroba, um nome, e um ou mais ".algo" |
A regex confere só o desenho. 123.456.789-09 tem formato de CPF, mas um CPF de verdade também precisa que os dois últimos dígitos (os verificadores) batam com uma conta feita sobre os nove primeiros; isso se faz com código comum, não com regex. Do mesmo jeito, um e-mail com formato certo pode não existir. E a regex de e-mail "perfeita" é enorme: no dia a dia, uma simples como esta basta, e a confirmação de verdade é mandar um e-mail.
Formas diferentes de usar regex
| Forma | Quando usar |
|---|---|
texto.matches(regex) | Validar uma vez, rapidinho. |
Pattern.matches(regex, texto) | O mesmo, escrito como método estático. |
Pattern p = Pattern.compile(regex) guardado numa constante e p.matcher(t).matches() | Validar muitos textos: a regex é preparada uma vez só (o String.matches prepara de novo a cada chamada). |
Matcher + while (m.find()) | Achar todos os pedaços que casam. |
m.group(n) / m.group("nome") | Separar as partes do que casou. |
replaceAll / replaceFirst | Trocar pelo molde (com $1 para reaproveitar grupos). |
split(regex) | Cortar o texto. |
contains, startsWith, replace | Quando o texto é fixo: são mais simples e não têm surpresas. |
Quando dá errado
Barra simples: illegal escape character
Escrever "\d+" dá erro de compilação (seção 1). Conserto: dobre a barra: "\\d+".
Regex mal escrita: PatternSyntaxException
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("(\\d+");
System.out.println("não chega aqui");
}
}
$ javac Main.java && java Main Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed group near index 4 (\d+ at java.base/java.util.regex.Pattern.error(Pattern.java:2205) at java.base/java.util.regex.Pattern.accept(Pattern.java:2055) at java.base/java.util.regex.Pattern.group0(Pattern.java:3233) at java.base/java.util.regex.Pattern.sequence(Pattern.java:2301) at java.base/java.util.regex.Pattern.expr(Pattern.java:2246) at java.base/java.util.regex.Pattern.compile(Pattern.java:1946) at java.base/java.util.regex.Pattern.<init>(Pattern.java:1577) at java.base/java.util.regex.Pattern.compile(Pattern.java:1102) at Main.main(Main.java:5)
O que quer dizer: o javac não confere a regex (para ele, é só um texto entre aspas). Quem confere é o Pattern.compile, com o programa já rodando. "Grupo não fechado perto da posição 4": abriu um parêntese e não fechou. A segunda linha mostra a regex, e a última linha do rastro (Main.java:5) mostra onde, no seu código. Como consertar: feche o grupo: "(\\d+)".
Outro clássico é usar um símbolo especial sozinho. "*promo*".split("*") dá PatternSyntaxException: Dangling meta character '*' near index 0 ("metacaractere solto"): o * quer dizer "repetir o anterior", e não há anterior. Para um asterisco de verdade: split("\\*").
O resultado não é o que você esperava
split(".")devolve[]: o ponto é "qualquer caractere". Use"\\.".replaceAll("R$", …)não troca nada:$é "fim do texto". Usereplace(sem regex) ou"R\\$".matchesdáfalsepara um texto que "contém" o molde:matchesexige o texto todo. Usefind.\wnão casa comç,ã,é: use\p{L}.
Referência
| Método | Devolve | Para quê |
|---|---|---|
s.matches(re) | boolean | O texto todo casa? |
s.replaceAll(re, troca) | String | Troca todas as ocorrências ($1 = grupo 1) |
s.replaceFirst(re, troca) | String | Troca a primeira |
s.split(re) | String[] | Corta onde casar |
Pattern.compile(re) | Pattern | Prepara a regex (pode lançar PatternSyntaxException) |
Pattern.compile(re, Pattern.CASE_INSENSITIVE) | Pattern | Ignorando maiúsculas/minúsculas |
p.matcher(texto) | Matcher | Leitor para um texto |
m.matches() / m.find() | boolean | Tudo casa? / Próximo pedaço |
m.group(), m.group(n), m.group("nome") | String | O pedaço / um grupo |
m.start(), m.end() | int | Posições do pedaço |
Exercícios
1. Complete a regex (como fica no Java) para "um ou mais dígitos":
2. Complete para cortar a frase em palavras, separando por um ou mais espaços:
3. Complete o método que procura o próximo pedaço que casa:
4. O que "a.b.c".split(".") devolve?
5. Uma regex com um parêntese sem fechar, como "(\\d+", dá erro quando?
Missão final: o cupom da padaria tem três preços. Use Pattern, Matcher e find() para achar cada preço no formato R$ 4,50, mostrar Preço: R$ 4,50 e somar tudo em centavos (Total em centavos: 1775). Dica: com a regex "R\\$ (\\d+),(\\d{2})", o grupo 1 são os reais e o grupo 2 os centavos.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String cupom = "Café R$ 4,50; pão R$ 1,25; bolo R$ 12,00";
int centavos = 0;
// crie o Pattern, o Matcher e o laço com find()
System.out.println("Total em centavos: " + centavos);
}
}
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String cupom = "Café R$ 4,50; pão R$ 1,25; bolo R$ 12,00";
Pattern preco = Pattern.compile("R\\$ (\\d+),(\\d{2})");
Matcher m = preco.matcher(cupom);
int centavos = 0;
while (m.find()) {
int reais = Integer.parseInt(m.group(1));
int cents = Integer.parseInt(m.group(2));
System.out.println("Preço: " + m.group());
centavos = centavos + reais * 100 + cents;
}
System.out.println("Total em centavos: " + centavos);
}
}
Saída real da solução:
$ java Main.java Preço: R$ 4,50 Preço: R$ 1,25 Preço: R$ 12,00 Total em centavos: 1775
Resumo
- Uma regex descreve um formato:
\ddígito,[A-Z]faixa,+"um ou mais",{5}"cinco vezes",( )grupo. - No código Java, cada
\da regex vira\\:"\\d+". Esquecer dáillegal escape character. matchesvalida o texto inteiro;Matcher.findprocura pedaços;groupsepara as partes.replaceAll,replaceFirstesplitaceitam regex;replacenão. Cuidado com.e$.- Regex confere formato (CEP, CPF, e-mail), não validade.
- Regex inválida só é descoberta ao rodar:
PatternSyntaxException.
Próxima aula: Anotações.