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ó".

Você vai aprender
  • Os blocos de uma regex: \d, [A-Z], +, {5}, ( )…
  • Por que no Java se escreve "\\d+", com duas barras.
  • String.matches, replaceAll, replaceFirst e split com regex.
  • Pattern e Matcher: find para 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 0 a 9.
{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
LinhaResultadoPor quê
"01310-100" com \d{5}-\d{3}true5 dígitos, hífen, 3 dígitos.
"1310-100"falseSó 4 dígitos antes do hífen.
"01310100"falseFalta o hífen.
"01310-100" com \d{5}-?\d{3}trueO ? torna o hífen opcional (zero ou uma vez).
"01310100" com \d{5}-?\d{3}trueSem 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:

  1. 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 um String, escreve-se \\.
  2. 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:

Este código tem um erro de propósito.
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.

RegexNo JavaCasa comExemplo que casa
abc"abc"Exatamente esses caracteresabc
."."Qualquer caractere (um)a, 7, -
\d"\\d"Um dígito 0–95
\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 listae
[A-Z]"[A-Z]"Um caractere da faixa (maiúsculas)Q
[^0-9]"[^0-9]"Um caractere fora da listax
\."\\."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 vezesx, xxxx
x{3}"x{3}"Exatamente 3 vezesxxx
x{2,4}"x{2,4}"De 2 a 4 vezesxx, xxxx
a|b"a|b"a ou bb
( )"(…)"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
ChamadaO 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 com Pattern.compile("…").
  • Matcher ("casador"): um "leitor" que percorre um texto procurando o padrão. Você cria com padrao.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
LinhaO 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
FormatoRegex (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"
Formato não é validade

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

FormaQuando 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 / replaceFirstTrocar pelo molde (com $1 para reaproveitar grupos).
split(regex)Cortar o texto.
contains, startsWith, replaceQuando 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

Este código tem um erro de propósito.
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". Use replace (sem regex) ou "R\\$".
  • matches dá false para um texto que "contém" o molde: matches exige o texto todo. Use find.
  • \w não casa com ç, ã, é: use \p{L}.

Referência

MétodoDevolvePara quê
s.matches(re)booleanO texto todo casa?
s.replaceAll(re, troca)StringTroca todas as ocorrências ($1 = grupo 1)
s.replaceFirst(re, troca)StringTroca a primeira
s.split(re)String[]Corta onde casar
Pattern.compile(re)PatternPrepara a regex (pode lançar PatternSyntaxException)
Pattern.compile(re, Pattern.CASE_INSENSITIVE)PatternIgnorando maiúsculas/minúsculas
p.matcher(texto)MatcherLeitor para um texto
m.matches() / m.find()booleanTudo casa? / Próximo pedaço
m.group(), m.group(n), m.group("nome")StringO pedaço / um grupo
m.start(), m.end()intPosições do pedaço

Exercícios

1. Complete a regex (como fica no Java) para "um ou mais dígitos":

"123".matches("")

2. Complete para cortar a frase em palavras, separando por um ou mais espaços:

String[] palavras = frase.split("");

3. Complete o método que procura o próximo pedaço que casa:

while (m.) { System.out.println(m.group()); }

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: \d dí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.
  • matches valida o texto inteiro; Matcher.find procura pedaços; group separa as partes.
  • replaceAll, replaceFirst e split aceitam regex; replace nã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.