↓ Skip to main content

JFR In-Process Data Redaction: JEP 536 in Java 27

·4 mins

JDK Flight Recorder has always captured more than you’d think to ask for. Command-line arguments, environment variables, system properties — all useful for figuring out why a JVM is behaving strangely, all written verbatim into the recording file. Which is fine until someone attaches that .jfr file to a support ticket and it turns out --dbpassword was one of the arguments.

JEP 536 fixes this in JDK 27 by redacting sensitive values inside the JVM, before they’re ever written to the recording — not by scrubbing the file afterward and hoping nobody looked at it first.

The problem, concretely
#

This is what used to end up in a recording, untouched:

export ACCESS_TOKEN=SECRET_TOKEN
java -XX:StartFlightRecording:filename=dump.jfr \
     -Xmx2G \
     -Djavax.net.ssl.keyStorePassword=SECRET_PASSWORD \
     -jar application.jar \
     --dbpassword ANOTHER_SECRET_PASSWORD

Three different secrets, three different places they leak from: an environment variable, a system property, and a plain CLI argument. Every one of them lands in jdk.InitialEnvironmentVariable, jdk.InitialSystemProperty, or jdk.JVMInformation events inside the recording, in plain text.

How it actually redacts
#

JFR now recognizes sensitive key/value pairs and arguments as it generates the events, and writes [REDACTED] in place of the value.

That distinction matters: a post-hoc scrubber can always miss a field someone adds later, or get skipped if someone reads the raw event stream instead of the final file. Redacting at the source means there’s nothing downstream to forget to scrub.

It’s on by default
#

You don’t have to configure anything to get baseline protection — JFR ships with a default set of glob patterns it already treats as sensitive:

Applies to Default patterns
redact-key (env vars, system properties) *api*key*, *auth*, *client*secret*, *credential*, *jaas*config*, *passphrase*, *passwd*, *password*, *private*key*, *pwd*, *secret*, *token*
redact-argument (CLI arguments) Same list, minus *auth*
redact-argument deliberately drops *auth* from its default list. *auth* on redact-key catches things like AUTH_TOKEN just fine, but on command-line arguments the same glob would also match --author, --authority, and anything else that merely contains “auth” — a false positive on ordinary, non-sensitive flags. Worth remembering if you add your own custom patterns: broader globs are more likely to swallow legitimate data the wider they get.

Configuring it yourself
#

Both knobs hang off -XX:FlightRecorderOptions, as sub-options separated by commas — each sub-option’s own filters are separated by semicolons:

export CONFIDENTIAL=SOME_SECRET
java -XX:FlightRecorderOptions:'redact-key=confidential,redact-argument=https://*:*@*' \
     -XX:StartFlightRecording:filename=dump.jfr \
     -Dconfidential=ANOTHER_SECRET \
     -jar application.jar https://john:[email protected]/login --verbose

That single redact-argument=https://*:*@* pattern is doing real work — it catches credentials embedded in URLs, a shape the default list doesn’t cover.

Matching more than one argument. A pattern can span the flag and its value(s) as separate space-separated tokens:

# matches `--password` followed by exactly one argument
java -XX:FlightRecorderOptions:'redact-argument=--password *' ...

# matches `--password` followed by exactly two arguments
java -XX:FlightRecorderOptions:'redact-argument=--password * *' ...

Loading filters from a file — useful once the list gets long enough that jamming it into a JVM flag stops being reasonable — with an @ prefix, one filter per line:

java '-XX:FlightRecorderOptions:[email protected],[email protected]' ...

Adding to the defaults instead of replacing them with a leading +:

java -XX:FlightRecorderOptions:'redact-key=+confidential;secret;@keys.txt' ...

Turning it off, if you need the old unredacted behavior back for some reason:

java -XX:FlightRecorderOptions:'redact-argument=none,redact-key=none' ...

Checking it actually worked
#

Two ways to verify redaction is doing what you expect, before you ship a recording anywhere:

# debug logging while the JVM runs
java -Xlog:jfr+redact=debug ...

# inspect the events most likely to carry secrets
jfr print --events InitialSystemProperty,JVMInformation,StringFlag,InitialEnvironmentVariable dump.jfr

The catch
#

Recordings taken on JDK 27 will look different from recordings taken on earlier versions, by design — anywhere a secret used to show up in plain text, you now get [REDACTED]. If you have existing tooling that parses jdk.InitialEnvironmentVariable or similar events and expects real values, JEP 536 will break it silently rather than loudly, since [REDACTED] is still a valid string in that field. Worth auditing before the upgrade, not after.

For the record, the previous unredacted behavior is still one flag away (redact-argument=none,redact-key=none from above) — nothing here is a one-way door.