java 57 lines · 8 steps

Masking sensitive data with regex in Java

A utility class redacts card numbers, emails, and SSNs from text while preserving just enough to stay useful.

Explained by highlit
1public final class SensitiveDataMasker {
2 
3 private static final Pattern CARD_NUMBER = Pattern.compile("\\b(?:\\d[ -]*?){13,19}\\b");
4 private static final Pattern EMAIL = Pattern.compile("[A-Za-z0-9._%+-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})");
5 private static final Pattern SSN = Pattern.compile("\\b\\d{3}-\\d{2}-(\\d{4})\\b");
6 
7 private SensitiveDataMasker() {
8 }
9 
10 public static String mask(String input) {
11 if (input == null || input.isEmpty()) {
12 return input;
13 }
14 String result = maskCardNumbers(input);
15 result = maskEmails(result);
16 result = maskSsns(result);
17 return result;
18 }
19 
20 private static String maskCardNumbers(String input) {
21 Matcher matcher = CARD_NUMBER.matcher(input);
22 StringBuilder sb = new StringBuilder();
23 while (matcher.find()) {
24 String digits = matcher.group().replaceAll("[ -]", "");
25 if (isLuhnValid(digits)) {
26 String last4 = digits.substring(digits.length() - 4);
27 matcher.appendReplacement(sb, Matcher.quoteReplacement("****-****-****-" + last4));
28 } else {
29 matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group()));
30 }
31 }
32 matcher.appendTail(sb);
33 return sb.toString();
34 }
35 
36 private static String maskEmails(String input) {
37 return EMAIL.matcher(input).replaceAll("***@$1");
38 }
39 
40 private static String maskSsns(String input) {
41 return SSN.matcher(input).replaceAll("***-**-$1");
42 }
43 
44 private static boolean isLuhnValid(String digits) {
45 int sum = 0;
46 boolean doubleDigit = false;
47 for (int i = digits.length() - 1; i >= 0; i--) {
48 int d = digits.charAt(i) - '0';
49 if (doubleDigit && (d *= 2) > 9) {
50 d -= 9;
51 }
52 sum += d;
53 doubleDigit = !doubleDigit;
54 }
55 return sum % 10 == 0;
56 }
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Compiling patterns once as static finals avoids recompiling regex on every call.
  2. 2Validating a card with Luhn before masking prevents false positives on random digit runs.
  3. 3Capturing groups in replacement strings lets you redact part of a match while keeping the rest.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Masking sensitive data with regex in Java — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code