json-masker: Zero-Dependency JSON Masking Library Claims 11x Speedup Over Jackson
This article introduces json-masker, a lightweight (~60 KB) zero-dependency Java library for masking sensitive fields in JSON, demonstrating up to 11x higher throughput than Jackson-based approaches with support for JSONPath, streaming API, allowlist/blocklist modes, and customizable masking strategies.
Introduction
In Spring Boot projects, masking sensitive data in logs and API responses is a standard requirement. Traditional solutions often bind tightly to heavy serialization libraries like Jackson, adding bundle size and introducing performance bottlenecks under high concurrency due to reflection and temporary object allocation. The open-source library json-masker offers a different approach: a single-pass scanning core with zero external dependencies, designed for high-throughput masking in the Spring Boot ecosystem.
Feature Comparison
The article compares json-masker against two common alternatives: Jackson with manual handling and regular expressions.
Performance: json-masker ~454,000 ops/sec; Jackson + manual ~40,000 ops/sec; Regex ~5,000 ops/sec.
Convenient API: json-masker ✅; Jackson + manual ❌ (requires manual implementation); Regex ❌ (requires manual implementation).
Zero dependencies: json-masker ✅; Jackson + manual ❌; Regex ✅.
JSONPath support: json-masker ✅; Jackson + manual ⚠️ (requires extra library); Regex ❌.
Streaming API: json-masker ✅; Jackson + manual ✅; Regex ❌.
Preserves original formatting: json-masker ✅; Jackson + manual ❌; Regex ✅.
Size: json-masker ~60 KB; Jackson + manual ~2.5 MB+; Regex N/A.
Quick Start
Environment: Spring Boot 3.5.0
Maven Dependency
<dependency>
<groupId>dev.blaauwendraad</groupId>
<artifactId>json-masker</artifactId>
<version>1.1.4</version>
</dependency>Basic Usage
var jsonMasker = JsonMasker.getMasker(Set.of("email", "password"));
String masked = jsonMasker.mask("""
{"name": "Pack", "email": "[email protected]", "password": "123123"}
""");
System.err.println(masked);Output:
{"name": "Pack", "email": "***", "password": "***"}Note: Reuse the JsonMasker instance; it pre-processes keys for optimal performance.
JsonMasker Instance Creation
Several factory methods are available via JsonMaskingConfig.builder():
Blocklist mode (keys): JsonMasker.getMasker(Set.of("email", "iban")) — masks specified keys recursively at any nesting level.
Blocklist mode (JSONPath):
maskJsonPaths(Set.of("$.email", "$.nested.iban", "$.organization.*.name"))— masks only at the exact paths; wildcards (*) supported for array elements.
Allowlist mode (keys): allowKeys(Set.of("id", "name")) — masks everything except the listed keys.
Allowlist mode (JSONPath):
allowJsonPaths(Set.of("$.id", "$.clients.*.phone", "$.nested.name")).
Key and JSONPath modes can be mixed. Simple keys apply recursively; JSONPath targets specific locations only.
Default Masking Behavior
With default configuration (block mode), the library replaces values with type-specific placeholders (e.g., strings become "***", numbers become 0, booleans become false). The article includes screenshots showing before/after JSON for a sample payload containing fields like email, age, visaApproved, iban, and billingAddress.
Allowlist (Whitelist) Example
var jsonMasker = JsonMasker.getMasker(
JsonMaskingConfig.builder()
.allowKeys(Set.of("orderId", "id", "travelPurpose", "successful"))
.build());
String maskedJson = jsonMasker.mask(json);Only the allowed keys remain visible; all others are masked.
Custom Mask Characters
Any type's default mask can be overridden:
var jsonMasker = JsonMasker.getMasker(
JsonMaskingConfig.builder()
.maskKeys(Set.of("email", "age", "visaApproved", "iban", "billingAddress"))
.maskStringsWith("[redacted]")
.maskNumbersWith("[redacted]")
.maskBooleansWith("[redacted]")
.build());Streaming API
For large JSON payloads, a streaming API avoids loading the entire document into memory:
var jsonMasker = JsonMasker.getMasker(
JsonMaskingConfig.builder()
.maskKeys(Set.of("email", "iban"))
.build());
jsonMasker.mask(jsonInputStream, jsonOutputStream);All masking features work identically in streaming and in-memory modes.
JSONPath Details and Limitations
JSONPath enables precise control over nested structures. The following features are not supported :
Descendants (..)
Recursive descent
Name selectors
Array slice selectors
Index selectors
Filter selectors
Function extensions
Escape characters
Additional constraints:
Numeric keys are not allowed.
JSONPath keys must not contain ambiguous segments sharing the same path prefix (e.g., $.payment.iban and $.payment.*.address conflict at segment 2; $.payment.iban and $.customerDetails.*.address are allowed).
A JSONPath cannot end with a leading wildcard; use $.a instead of $.a.*.
JSONPath Example
var jsonMasker = JsonMasker.getMasker(
JsonMaskingConfig.builder()
.maskJsonPaths(Set.of(
"$.customerDetails.email",
"$.customerDetails.age",
"$.customerDetails.visaApproved",
"$.payment.iban",
"$.payment.billingAddress",
"$.customerDetails.identificationDocuments.*.number"
))
.build());
String maskedJson = jsonMasker.mask(json);The article includes a screenshot of the masked output for this configuration.
Length-Preserving Masking
To retain the original string length or digit count, use character-level masking:
var jsonMasker = JsonMasker.getMasker(
JsonMaskingConfig.builder()
.maskKeys(Set.of("email", "age", "visaApproved", "iban", "billingAddress"))
.maskStringCharactersWith("*")
.maskNumberDigitsWith(8)
.build());
String maskedJson = jsonMasker.mask(json);Strings are replaced with asterisks of the same length; numbers are replaced with the digit 8 repeated to match the original digit count. A screenshot illustrates the result.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
