Why are passwords and secrets handled as char[] rather than String in Java, and how do you clear them?
answer
- String is immutable -> can't erase, lingers till GC, may intern
- char[] is mutable -> Arrays.fill(arr,'\0') in finally
- JDK signals intent: Console.readPassword/PBEKeySpec/getPassword -> char[]
- Converting to String recreates the un-erasable copy
- Best-effort: copies/swap/GC-moves still leak; minimize possession
basics
~20 sStrings in Java are immutable and you can't erase them, so a secret stays in memory until garbage collection happens, exposed in heap dumps. A char[] is mutable, so you can overwrite it with Arrays.fill right after use to shorten the exposure.
solid answer
~50 sA Java String is immutable: you cannot zero it, and it lingers on the heap until GC reclaims it (and may even be interned), so any heap dump or memory-scrape can recover the secret. A char[] is mutable, so the standard hygiene is to read the secret into a char[], use it immediately, and Arrays.fill(arr, '\0') it in a finally block to minimize the window it sits in cleartext. This is why JDK APIs like Console.readPassword() and PBEKeySpec(char[]) take char[], and JPasswordField.getPassword() returns one. It's a best-effort defense in depth, not absolute: bytes may have been copied, the JIT/GC can move objects, and JNI/swap can leak — but it meaningfully reduces lifetime. For at-rest secrets, prefer not holding them at all: inject config from a vault/env at use time, and for sensitive byte buffers consider off-heap or wiping after use.
code
java · 14 lineschar[] pw = System.console().readPassword("Password: ");
try {
// use the secret immediately (e.g. derive a key)
PBEKeySpec spec = new PBEKeySpec(pw, salt, iterations, keyLen);
try {
SecretKey key = factory.generateSecret(spec);
// ...
} finally {
spec.clearPassword(); // wipe the spec's internal copy
}
} finally {
java.util.Arrays.fill(pw, '\0'); // wipe our copy ASAP
}
// Avoid: String s = new String(pw); // immutable, un-erasable, may intern/leakgo deeper
Knows passwords should be char[] not String and that you can overwrite a char[] but not a String.
Explains immutability + GC non-determinism + heap-dump exposure, and wipes with Arrays.fill in a finally.
Frames it as best-effort defense in depth, cites JDK char[] APIs, and notes copies/swap/JIT-moves and String-conversion pitfalls.
Prioritizes not possessing secrets (vault/short-lived injection), defines org-wide secret-handling and logging/masking standards, and weighs off-heap storage.
## The problem: secrets that won't go away When your program handles a **secret** — a password, an API key, a private key passphrase — you want it in memory for the shortest possible time and erased afterward, so that a **heap dump** (a snapshot of the JVM's memory, e.g. an `.hprof` file from a crash or `jmap`), a core dump, or a memory-scraping attacker can't recover it. ### Why `String` is the wrong container A Java **`String` is immutable**: once created, its character data cannot be changed. Consequences for secrets: 1. **You cannot erase it.** There is no `clear()`; you can only drop the reference and *hope* the **garbage collector (GC)** — the runtime subsystem that reclaims unused objects — eventually reclaims and overwrites that memory. "Eventually" is non-deterministic; it may be seconds or minutes, during which the cleartext sits on the heap. 2. **It may be interned / pooled.** String literals (and `intern()`ed strings) live in a shared pool for the JVM's lifetime, so an accidentally-literal secret could persist indefinitely. 3. **It shows up plainly in heap dumps and logs.** A `String` is trivially readable in an `.hprof`, and its `toString()` is itself, so it leaks easily into log lines. ### Why `char[]` is preferred A **`char[]` (character array) is mutable**. That single property is the win: after you've used the secret you can **overwrite every element** so the cleartext is gone immediately rather than at the GC's whim: ```java char[] pw = console.readPassword(); try { authenticate(pw); } finally { java.util.Arrays.fill(pw, '\0'); // zero it out ASAP } ``` `Arrays.fill(pw, '\0')` writes a null character into each slot, shrinking the window in which the secret exists in cleartext from "until GC" down to "until this finally block." This is precisely why the JDK's secret-handling APIs traffic in `char[]`: - `java.io.Console.readPassword()` returns `char[]`. - `javax.crypto.spec.PBEKeySpec(char[] password)` takes — and exposes `clearPassword()` to wipe — a `char[]`. - Swing's `JPasswordField.getPassword()` returns `char[]` (its `getText()` is deprecated for exactly this reason). ## How strong is this defense, really? It is **best-effort defense in depth, not a guarantee**: - The secret may have been **copied** before you got the `char[]` (network buffers, the bytes the password came in on, decoding intermediates). Wiping your copy doesn't wipe theirs. - The **JIT/GC can relocate** objects (copying collectors), leaving stale copies in old memory regions you can't reach to overwrite. - The OS may have **swapped** the page to disk, or the value can leak through **JNI** / native code. - If you ever convert the `char[]` to a `String` (e.g. concatenation, logging, `new String(pw)`), you've recreated the immutable, un-erasable copy and lost the benefit. So `char[]` + `Arrays.fill` *reduces the exposure window*; it does not make the secret unrecoverable. Treat it as one layer. ## The bigger picture: minimize possession The most effective move is to **hold secrets for as little code and time as possible**, and ideally not in your own data structures at all: - **Inject configuration secrets** (DB passwords, API keys) from a **secret manager / vault** or environment at the point of use, rather than loading them eagerly and keeping them resident. (This also addresses hardcoded-secret avoidance — secrets belong in externalized config, never in source.) - For raw sensitive **bytes**, consider **off-heap / direct buffers** you can deterministically wipe, or libraries designed for the purpose. - Never log secrets; mask them in `toString()`; keep them out of exception messages and URLs. ## Connection to the other AppSec rules This is the in-memory cousin of **don't hardcode secrets**: both say secrets must have *bounded existence* — short-lived in memory (`char[]` + wipe) and externally managed at rest (config/vault, not source). The unifying idea across this whole concept-bridge leaf is controlling *where untrusted data and sensitive data are allowed to live and be interpreted*.
- If char[] only reduces the window, why bother — what threats does it actually mitigate?It shrinks how long the cleartext sits on the heap, so a heap/core dump, post-crash forensics, or memory scrape taken after the wipe is far less likely to contain it. It's a cheap layer that meaningfully lowers exposure, not a guarantee against an attacker who already had access during use.
- What undoes the char[] benefit instantly?Turning it into a String — e.g. new String(pw), string concatenation, or logging it — creates an immutable, un-erasable copy that lingers until GC and can be interned/dumped.
saying these in an interview costs you the question
- Claiming char[] makes a secret truly unrecoverable (it's only best-effort)
- Storing or logging the secret as a String 'just briefly'
- Forgetting to wipe in a finally (exceptions skip the clear)
- Hardcoding the secret in source rather than externalizing it