Regex #
Kamu punya ribuan baris log server dan perlu mengekstrak semua alamat IP. Atau kamu harus memvalidasi format nomor telepon dari input pengguna sebelum disimpan ke database. Atau kamu perlu mengganti semua format tanggal DD/MM/YYYY dalam dokumen menjadi YYYY-MM-DD. Semua ini bisa diselesaikan dalam beberapa baris kode dengan regex. Regex (Regular Expression) adalah bahasa mini untuk mendeskripsikan pola teks — sekali kamu menguasainya, banyak masalah pemrosesan string yang tadinya rumit menjadi sepele. Java menyediakan dukungan regex lengkap melalui java.util.regex dengan dua kelas utama: Pattern dan Matcher.
Gambaran Umum #
Sebelum masuk ke sintaks, ada tiga konsep yang perlu dipahami:
Pola (Pattern) adalah ekspresi yang mendeskripsikan apa yang dicari. Misalnya \d+ berarti “satu atau lebih digit angka”, dan [a-z]{3} berarti “tepat tiga huruf kecil”.
Kompilasi adalah proses mengubah string pola menjadi objek Pattern yang siap dipakai. Ini operasi yang tidak murah — selalu kompilasi sekali dan simpan hasilnya sebagai konstanta jika pola yang sama dipakai berulang.
Pencocokan (Matching) adalah proses menerapkan Pattern ke teks target lewat objek Matcher. Matcher menyimpan posisi saat ini dalam teks dan menyediakan metode untuk mencari, mengekstrak, dan mengganti.
flowchart LR
A["String pola\n\"\\\\d+\""] -->|"Pattern.compile()"| B["Pattern\n(dikompilasi)"]
B -->|"pattern.matcher(teks)"| C["Matcher\n(posisi dalam teks)"]
C -->|"find() / matches()"| D["Hasil:\ngroup(), start(), end()"]Di Java, backslash\dalam string harus ditulis double:\\. Jadi pola regex\d(satu digit) ditulis sebagai"\\d"dalam kode Java. Ini sering menjadi sumber kebingungan di awal.
Sintaks Dasar Regex #
Sebelum menggunakan Pattern dan Matcher, kamu perlu memahami “bahasa” yang mereka gunakan. Regex punya kosakata kecil tapi kombinasinya sangat kuat.
Karakter Literal dan Titik #
a → cocok dengan huruf 'a' secara harfiah
abc → cocok dengan urutan "abc"
. → cocok dengan SEMBARANG karakter satu (kecuali newline)
\. → cocok dengan titik literal (titik harus di-escape)
Kelas Karakter #
Kelas karakter adalah daftar atau rentang karakter yang boleh cocok di posisi tertentu.
| Sintaks | Arti | Contoh cocok |
|---|---|---|
[abc] | Salah satu dari a, b, atau c | a, b, c |
[^abc] | Semua kecuali a, b, c | d, 1, ! |
[a-z] | Huruf kecil a sampai z | m, z |
[A-Z] | Huruf besar A sampai Z | B, X |
[0-9] | Digit 0 sampai 9 | 3, 9 |
[a-zA-Z0-9] | Huruf atau digit | g, 7 |
Shorthand Kelas Karakter #
Karena beberapa kelas karakter sangat sering dipakai, regex menyediakan shorthand:
| Shorthand | Setara dengan | Arti |
|---|---|---|
\d | [0-9] | Digit angka |
\D | [^0-9] | Bukan digit |
\w | [a-zA-Z0-9_] | Karakter “kata” (huruf, angka, underscore) |
\W | [^a-zA-Z0-9_] | Bukan karakter kata |
\s | [ \t\n\r\f] | Whitespace (spasi, tab, newline) |
\S | [^ \t\n\r\f] | Bukan whitespace |
Quantifier #
Quantifier menentukan berapa kali pola sebelumnya boleh muncul.
| Quantifier | Arti | Contoh |
|---|---|---|
* | 0 atau lebih | \d* cocok dengan "", "5", "123" |
+ | 1 atau lebih | \d+ cocok dengan "5", "123" tapi bukan "" |
? | 0 atau 1 (opsional) | colou?r cocok dengan "color" dan "colour" |
{n} | Tepat n kali | \d{4} cocok dengan "2025" |
{n,} | n kali atau lebih | \d{3,} cocok dengan "123", "12345" |
{n,m} | Antara n dan m kali | \d{2,4} cocok dengan "12", "123", "1234" |
Quantifier secara default greedy — mereka cocok sebanyak mungkin. Tambahkan ? di belakang untuk membuatnya lazy (cocok sesedikit mungkin): .*?, \d+?, \w{2,5}?.
Anchor #
Anchor tidak cocok dengan karakter, tapi dengan posisi dalam teks.
| Anchor | Posisi yang dicocokkan |
|---|---|
^ | Awal string (atau awal baris dalam mode multiline) |
$ | Akhir string (atau akhir baris dalam mode multiline) |
\b | Batas kata — posisi antara \w dan \W |
\B | Bukan batas kata |
^Java → "Java" hanya cocok jika ada di awal string
\.java$ → ".java" hanya cocok jika ada di akhir string
\bJava\b → "Java" sebagai kata utuh, tidak cocok dengan "JavaScript"
Alternasi dan Grup #
cat|dog → cocok dengan "cat" atau "dog"
(cat|dog)s → cocok dengan "cats" atau "dogs"
(ha)+ → cocok dengan "ha", "haha", "hahaha"
(?:ha)+ → seperti di atas tapi grup tidak ditangkap (non-capturing group)
Pattern dan Matcher #
Pattern dan Matcher adalah dua kelas utama di java.util.regex. Semua operasi regex yang serius di Java melewati keduanya.
Cara Kerja Dasar #
import java.util.regex.Pattern;
import java.util.regex.Matcher;
String teks = "Saya punya 3 kucing dan 12 ikan.";
String pola = "\\d+"; // satu atau lebih digit
// Kompilasi pola — lakukan sekali, simpan sebagai konstanta jika dipakai berulang
Pattern pattern = Pattern.compile(pola);
// Buat Matcher untuk teks target
Matcher matcher = pattern.matcher(teks);
// find() mencari kecocokan berikutnya, mengembalikan true jika ketemu
while (matcher.find()) {
System.out.println("Ditemukan: " + matcher.group());
System.out.println(" posisi: " + matcher.start() + " - " + matcher.end());
}
// Output:
// Ditemukan: 3
// posisi: 10 - 11
// Ditemukan: 12
// posisi: 22 - 24
matches() vs find() vs lookingAt() #
Tiga metode utama Matcher punya perilaku yang berbeda dan sering membingungkan pemula.
Pattern p = Pattern.compile("\\d+");
// matches(): teks SELURUHNYA harus cocok dengan pola — jarang dipakai langsung
Matcher m1 = p.matcher("123");
System.out.println(m1.matches()); // true — seluruh "123" adalah digit
Matcher m2 = p.matcher("123abc");
System.out.println(m2.matches()); // false — ada "abc" di belakang
// find(): cari kecocokan di MANA SAJA dalam teks — paling sering dipakai
Matcher m3 = p.matcher("abc 123 def");
System.out.println(m3.find()); // true — "123" ketemu di tengah
// lookingAt(): cocok dari AWAL teks, tapi tidak harus sampai akhir
Matcher m4 = p.matcher("123abc");
System.out.println(m4.lookingAt()); // true — dimulai dengan digit
Matcher m5 = p.matcher("abc123");
System.out.println(m5.lookingAt()); // false — tidak dimulai dengan digit
Flag Kompilasi #
Flag mengubah perilaku pencocokan pola.
// CASE_INSENSITIVE: abaikan huruf besar/kecil
Pattern pInsensitive = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
System.out.println(pInsensitive.matcher("JAVA").matches()); // true
System.out.println(pInsensitive.matcher("Java").matches()); // true
// MULTILINE: ^ dan $ cocok di awal/akhir setiap baris, bukan hanya seluruh string
String multiline = "baris 1\nbaris 2\nbaris 3";
Pattern pMulti = Pattern.compile("^baris", Pattern.MULTILINE);
Matcher mMulti = pMulti.matcher(multiline);
int count = 0;
while (mMulti.find()) count++;
System.out.println(count); // 3 — ketemu di awal setiap baris
// DOTALL: titik (.) cocok juga dengan newline
Pattern pDotall = Pattern.compile("a.b", Pattern.DOTALL);
System.out.println(pDotall.matcher("a\nb").matches()); // true
// Gabung beberapa flag dengan OR bitwise
Pattern pGabung = Pattern.compile("java", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
// Flag juga bisa ditulis inline dalam pola
Pattern pInline = Pattern.compile("(?i)java"); // setara dengan CASE_INSENSITIVE
Grup Tangkapan #
Grup tangkapan adalah bagian pola yang dibungkus dengan tanda kurung (). Selain membantu struktur pola, grup menangkap teks yang cocok sehingga bisa diakses secara terpisah.
Grup Bernomor #
// Pola: (tahun)-(bulan)-(hari)
Pattern tanggal = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = tanggal.matcher("Tanggal lahir: 1995-07-20");
if (m.find()) {
System.out.println("Seluruh match: " + m.group(0)); // 1995-07-20
System.out.println("Tahun: " + m.group(1)); // 1995
System.out.println("Bulan: " + m.group(2)); // 07
System.out.println("Hari: " + m.group(3)); // 20
}
Grup Bernama #
Grup bernama lebih mudah dibaca dan tidak bergantung pada urutan — perubahan pola tidak mengubah cara mengakses grup.
// Sintaks grup bernama: (?<namaGrup>pola)
Pattern tanggal = Pattern.compile(
"(?<tahun>\\d{4})-(?<bulan>\\d{2})-(?<hari>\\d{2})"
);
Matcher m = tanggal.matcher("Tanggal: 2025-08-17");
if (m.find()) {
System.out.println("Tahun: " + m.group("tahun")); // 2025
System.out.println("Bulan: " + m.group("bulan")); // 08
System.out.println("Hari: " + m.group("hari")); // 17
}
Referensi Balik (Backreference) #
Backreference memungkinkan kamu merujuk kembali ke teks yang sudah ditangkap oleh grup sebelumnya dalam pola yang sama.
// \1 merujuk ke teks yang ditangkap grup pertama
// Pola ini cocok dengan kata yang diulang ("the the", "adalah adalah")
Pattern ulangi = Pattern.compile("\\b(\\w+)\\s+\\1\\b");
Matcher m = ulangi.matcher("ini adalah adalah contoh kata yang yang diulang");
while (m.find()) {
System.out.println("Kata diulang: " + m.group(1));
}
// Output:
// Kata diulang: adalah
// Kata diulang: yang
Lookahead dan Lookbehind #
Lookahead dan lookbehind adalah zero-width assertions — mereka memeriksa konteks di sekitar posisi saat ini tanpa ikut dalam hasil pencocokan.
Lookahead #
// Positive lookahead (?=...): cocok hanya jika diikuti pola tertentu
// Cari angka yang diikuti " USD"
Pattern harga = Pattern.compile("\\d+(?= USD)");
Matcher m = harga.matcher("Harga: 500 USD dan 300 EUR");
while (m.find()) {
System.out.println(m.group()); // 500 (hanya yang diikuti USD)
}
// Negative lookahead (?!...): cocok hanya jika TIDAK diikuti pola tertentu
// Cari angka yang TIDAK diikuti " USD"
Pattern bukanUSD = Pattern.compile("\\d+(?! USD)");
Matcher m2 = bukanUSD.matcher("Harga: 500 USD dan 300 EUR");
// akan cocok dengan "50" (bagian dari 500) dan "300" (diikuti EUR)
Lookbehind #
// Positive lookbehind (?<=...): cocok hanya jika didahului pola tertentu
// Cari angka yang didahului "Rp"
Pattern rupiah = Pattern.compile("(?<=Rp)\\d+");
Matcher m = rupiah.matcher("Harga: Rp50000 dan USD300");
while (m.find()) {
System.out.println(m.group()); // 50000
}
// Negative lookbehind (?<!...): cocok hanya jika TIDAK didahului pola tertentu
Pattern bukanRupiah = Pattern.compile("(?<!Rp)\\d+");
Penggantian Teks #
Matcher menyediakan metode untuk mengganti teks yang cocok — baik semua kemunculan maupun satu per satu dengan logika kustom.
replaceAll dan replaceFirst #
// replaceAll via String (shortcut, tidak perlu buat Pattern/Matcher)
String teks = "Nomor HP: 0812-3456-7890 dan 0856-1234-5678";
String disensor = teks.replaceAll("\\d", "*");
System.out.println(disensor);
// Nomor HP: ****-****-**** dan ****-****-****
// replaceFirst: hanya ganti kemunculan pertama
String hanyaPertama = teks.replaceFirst("\\d+", "XXXX");
System.out.println(hanyaPertama);
// Nomor HP: XXXX-3456-7890 dan 0856-1234-5678
// Gunakan referensi grup dalam replacement: $1, $2, dst
String tanggal = "Tanggal: 17/08/2025";
// Ubah format DD/MM/YYYY → YYYY-MM-DD
String iso = tanggal.replaceAll("(\\d{2})/(\\d{2})/(\\d{4})", "$3-$2-$1");
System.out.println(iso); // Tanggal: 2025-08-17
Penggantian dengan Logika Kustom #
// Java 9+: replaceAll(Function<MatchResult, String>) untuk logika kustom
Pattern angka = Pattern.compile("\\d+");
Matcher m = angka.matcher("Apel 5 buah, Mangga 12 buah, Jeruk 3 buah");
// Gandakan semua angka
String hasil = m.replaceAll(mr -> String.valueOf(Integer.parseInt(mr.group()) * 2));
System.out.println(hasil);
// Apel 10 buah, Mangga 24 buah, Jeruk 6 buah
Split dengan Regex #
String.split() menerima regex sebagai delimiter, jauh lebih fleksibel dari split berbasis karakter literal.
Memecah dengan Pola #
// Split berdasarkan satu atau lebih whitespace
String kalimat = " Ini adalah teks dengan spasi ganda ";
String[] kata = kalimat.trim().split("\\s+");
System.out.println(kata.length); // 6
// [Ini, adalah, teks, dengan, spasi, ganda]
// Split berdasarkan koma dengan spasi opsional
String csv = "Budi, Ani,Citra ,Doni";
String[] nama = csv.split("\\s*,\\s*");
// [Budi, Ani, Citra, Doni] — spasi di sekitar koma ikut dihapus
// Split berdasarkan beberapa delimiter sekaligus
String campuran = "satu;dua,tiga|empat";
String[] bagian = campuran.split("[;,|]");
// [satu, dua, tiga, empat]
// Batasi jumlah bagian dengan parameter limit
String log = "ERROR:NullPointerException:line 42:detail panjang";
String[] parts = log.split(":", 3); // maks 3 bagian
// [ERROR, NullPointerException, line 42:detail panjang]
Performa dan Anti-Pattern #
Regex yang ditulis ceroboh bisa sangat lambat atau bahkan menyebabkan program hang. Beberapa anti-pattern umum perlu kamu hindari.
Kompilasi Ulang di Dalam Loop #
// ANTI-PATTERN: Pattern.compile() dipanggil setiap iterasi — sangat mahal
List<String> emails = daftarEmail();
for (String email : emails) {
if (Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$").matcher(email).matches()) {
// proses...
}
}
// BENAR: kompilasi sekali sebagai konstanta static
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
for (String email : emails) {
if (EMAIL_PATTERN.matcher(email).matches()) {
// proses...
}
}
Catastrophic Backtracking #
// ANTI-PATTERN: pola dengan nested quantifier dan alternasi yang overlap
// (a+)+ atau (a|aa)+ pada input panjang bisa berjalan eksponensial
Pattern berbahaya = Pattern.compile("(a+)+b");
// "aaaaaaaaaaaaaaaaac" akan menyebabkan program hang karena backtracking tak terbatas
// BENAR: hindari nested quantifier, gunakan atomic group atau possessive quantifier
// Atau gunakan pola yang lebih spesifik
Pattern aman = Pattern.compile("a+b"); // simple, tidak ada nesting
Greedy vs Lazy — Pilih yang Tepat #
String html = "<b>teks tebal</b> dan <i>miring</i>";
// ANTI-PATTERN: greedy mencocokkan TERLALU BANYAK
Pattern greedy = Pattern.compile("<.+>");
Matcher m1 = greedy.matcher(html);
if (m1.find()) System.out.println(m1.group());
// Output: <b>teks tebal</b> dan <i>miring</i> ← terlalu lebar!
// BENAR: lazy mencocokkan seminimal mungkin
Pattern lazy = Pattern.compile("<.+?>");
Matcher m2 = lazy.matcher(html);
while (m2.find()) System.out.println(m2.group());
// Output:
// <b>
// </b>
// <i>
// </i>
Kasus Nyata #
Validasi Email #
private static final Pattern EMAIL =
Pattern.compile("^[\\w.+-]+@[\\w-]+\\.[a-zA-Z]{2,}(?:\\.[a-zA-Z]{2,})?$");
public static boolean emailValid(String email) {
if (email == null) return false;
return EMAIL.matcher(email).matches();
}
System.out.println(emailValid("[email protected]")); // true
System.out.println(emailValid("[email protected]")); // true
System.out.println(emailValid("bukan-email")); // false
System.out.println(emailValid("@tanpa-user.com")); // false
Validasi Nomor Telepon Indonesia #
// Format: 08XX-XXXX-XXXX atau +628XX-XXXX-XXXX (tanda hubung opsional)
private static final Pattern TELEPON_ID = Pattern.compile(
"^(\\+62|0)8[1-9][0-9][-\\s]?[0-9]{3,4}[-\\s]?[0-9]{3,4}$"
);
public static boolean teleponValid(String nomor) {
if (nomor == null) return false;
return TELEPON_ID.matcher(nomor.trim()).matches();
}
System.out.println(teleponValid("081234567890")); // true
System.out.println(teleponValid("0812-3456-7890")); // true
System.out.println(teleponValid("+6281234567890")); // true
System.out.println(teleponValid("1234567890")); // false
Ekstraksi Semua URL dari Teks #
private static final Pattern URL = Pattern.compile(
"https?://[\\w.-]+(?:/[\\w./?=%&+-]*)?"
);
public static List<String> ekstrakURL(String teks) {
List<String> urls = new ArrayList<>();
Matcher m = URL.matcher(teks);
while (m.find()) {
urls.add(m.group());
}
return urls;
}
String artikel = "Kunjungi https://java.unisbadri.com dan https://docs.oracle.com/javase/ untuk referensi.";
List<String> ditemukan = ekstrakURL(artikel);
ditemukan.forEach(System.out::println);
// https://java.unisbadri.com
// https://docs.oracle.com/javase/
Ekstraksi Semua Alamat IP dari Log #
private static final Pattern IP = Pattern.compile(
"\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\b"
);
String log = """
2025-08-17 ERROR request dari 192.168.1.10 gagal
2025-08-17 INFO akses dari 10.0.0.254 berhasil
2025-08-17 WARN percobaan dari 999.999.999.999 ditolak
""";
Matcher m = IP.matcher(log);
while (m.find()) {
System.out.println("IP: " + m.group());
}
// IP: 192.168.1.10
// IP: 10.0.0.254
// (999.999.999.999 tidak cocok — nilai melebihi 255)
Format Ulang Tanggal #
// Ubah semua tanggal DD/MM/YYYY menjadi YYYY-MM-DD dalam dokumen
private static final Pattern TANGGAL_ID =
Pattern.compile("(?<hari>\\d{2})/(?<bulan>\\d{2})/(?<tahun>\\d{4})");
public static String formatUlang(String teks) {
return TANGGAL_ID.matcher(teks)
.replaceAll(mr -> mr.group("tahun") + "-" + mr.group("bulan") + "-" + mr.group("hari"));
}
String dokumen = "Kontrak ditandatangani 17/08/2025 dan berlaku hingga 31/12/2027.";
System.out.println(formatUlang(dokumen));
// Kontrak ditandatangani 2025-08-17 dan berlaku hingga 2027-12-31.
Referensi Cepat Simbol #
| Simbol | Arti |
|---|---|
. | Sembarang karakter (kecuali newline) |
\d | Digit [0-9] |
\D | Bukan digit |
\w | Karakter kata [a-zA-Z0-9_] |
\W | Bukan karakter kata |
\s | Whitespace |
\S | Bukan whitespace |
\b | Batas kata |
^ | Awal string |
$ | Akhir string |
* | 0 atau lebih |
+ | 1 atau lebih |
? | 0 atau 1 |
{n,m} | n sampai m kali |
[abc] | Salah satu dari a, b, c |
[^abc] | Bukan a, b, atau c |
(abc) | Grup tangkapan |
(?:abc) | Grup non-tangkapan |
(?<nama>abc) | Grup bernama |
a|b | a atau b |
(?=...) | Positive lookahead |
(?!...) | Negative lookahead |
(?<=...) | Positive lookbehind |
(?<!...) | Negative lookbehind |
Kapan Menggunakan Regex #
Gunakan REGEX jika:
✓ Pola yang dicari tidak bisa diekspresikan dengan operasi String biasa
✓ Perlu validasi format kompleks (email, telepon, IP, URL)
✓ Perlu mengekstrak banyak kecocokan dari teks panjang
✓ Perlu mengganti teks dengan pola kustom (format ulang tanggal, sensor data)
✓ Perlu split dengan delimiter yang bervariasi
Hindari REGEX jika:
✗ Operasi String biasa sudah cukup — contains(), startsWith(), indexOf() lebih cepat
✗ Memvalidasi HTML atau XML — gunakan parser khusus, regex tidak cocok untuk ini
✗ Pola yang sangat kompleks dan berlapis — pertimbangkan parser atau state machine
✗ Perlu maintainability tinggi — regex panjang sulit dibaca dan di-maintain
Tips performa:
✓ Selalu simpan Pattern.compile() sebagai konstanta static final
✓ Gunakan grup non-tangkapan (?:...) jika tidak perlu akses ke grup
✓ Hindari nested quantifier (a+)+ yang bisa menyebabkan catastrophic backtracking
✓ Gunakan anchor ^ dan $ saat memvalidasi seluruh string
Ringkasan #
PatterndanMatcheradalah pasangan utama —Pattern.compile(pola)mengompilasi pola sekali,pattern.matcher(teks)membuat objek yang beroperasi pada teks target.- Kompilasi pola sekali, simpan sebagai konstanta —
Pattern.compile()mahal. Jangan panggil di dalam loop. Gunakanprivate static final Pattern.find()vsmatches()—find()mencari kecocokan di mana saja dalam teks (paling sering dipakai),matches()membutuhkan seluruh teks cocok dengan pola.- Double backslash di Java — pola
\ddi regex ditulis"\\d"dalam String Java.\bditulis"\\b". Ini sumber kebingungan yang sangat umum.- Grup tangkapan — bungkus bagian pola dengan
()untuk mengaksesnya lewatgroup(n). Gunakan grup bernama(?<nama>pola)dangroup("nama")untuk kode yang lebih mudah dibaca.- Greedy vs lazy — quantifier greedy (
*,+) cocok sebanyak mungkin. Tambah?(*?,+?) untuk lazy yang cocok sesedikit mungkin. Pilih berdasarkan apa yang ingin ditangkap.replaceAlldengan referensi grup —$1,$2dalam string pengganti merujuk ke grup tangkapan. Berguna untuk mengubah format teks seperti DD/MM/YYYY → YYYY-MM-DD.- Hindari catastrophic backtracking — nested quantifier seperti
(a+)+pada input yang tidak cocok bisa berjalan eksponensial. Tulis pola yang spesifik dan hindari alternasi yang overlap.