Char Array to String â Zeroing Doesn't Protect Passwords
Heap dumps expose plaintext passwords after char[] to String â zeroing the array doesn't protect the copy.
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
- Core concept: four methods convert char[] to String, but differ in null handling and intent
- String constructor: most common, throws NPE on null
- String.valueOf(): returns literal "null" for null input â cleaner for safe defaults
- StringBuilder.append(): useful when building strings incrementally, not for direct conversion
- Performance insight: all four methods copy the char array â O(n) time, O(n) memory
- Production insight: String.valueOf(null) returns "null" string, not null â silent bug if you expect null propagation
A char array in Java is a sequence of individual characters. A String is an immutable object wrapping character data. Converting between them is a basic operation you'll need constantly â parsing input, processing text from legacy APIs, working with cryptography code that deals in char[] rather than String for security reasons.
char[] to String conversion comes up surprisingly often in real codebases. Security-conscious APIs (like Java's own KeyStore and JPasswordField) return passwords as char[] rather than String precisely because char arrays can be zeroed out after use, while String literals are interned and may linger in the heap. Understanding the conversion and knowing which method to use matters for correctness, not just convenience.
Why char[] to String Is a Security Footgun
Converting a char array to a String in Java is trivial â call new String(char[]) or String.valueOf(char[]). The core mechanic is that the String constructor copies the array contents into its internal, immutable byte[] (or char[] pre-Java 9). This means the original char array and the resulting String share no memory after construction. The problem: you cannot erase a String. Once created, its backing array lives in memory until garbage collection, and even then, the data may persist in heap dumps or swap files. Zeroing the source char array after conversion does nothing to the String's internal copy. This is why security guidelines (OWASP, CERT) mandate using char[] for passwords and clearing it immediately â but only if you never convert it to a String. The moment you call new String(passwordChars), you've created an immortal copy of the secret. In practice, this bites teams using libraries that accept only String parameters (e.g., JDBC connections, encryption APIs). The conversion is O(n) in time and space, but the real cost is the loss of control over secret lifetime.
new String(char[]) does not erase the String's internal copy. The secret remains in heap memory until GC â and possibly after.javax.security.auth.callback.PasswordCallback).new String(char[]) is theater; the copy persists.Four Methods for char[] to String Conversion
All four methods produce the same output for typical use cases. The differences matter at the margins: performance for large arrays, null-safety, and intent signalling. Here's a closer look at each one and where you'd use it in production.
- String constructor calls Arrays.copyOf internally â a full copy.
- String.valueOf(char[]) delegates to the same constructor.
- StringBuilder.append(char[]) copies into its own buffer first, then toString() copies again.
String.copyValueOf()is identical to valueOf â legacy alias.
Under the Hood: String Constructor vs String.valueOf()
Many engineers assume these two have different implementations. They don't. Open the OpenJDK source for String.class and you'll see valueOf(char data[]) simply returns new String(data). The only difference is how they handle null.
When you call new String((char[]) null), the JVM immediately throws NullPointerException before the constructor can even check the argument. But String.valueOf handles null differently: it returns the literal string "null". This is consistent with other valueOf methods in Java, but it often surprises developers expecting a null return.
So why have both? Intent. new String(chars) signals "I expect this to be non-null â fail early". String.valueOf(chars) signals "I accept null and want a safe string representation". Pick based on what your downstream code expects.
String() and handle null separately.String.copyValueOf()Partial Char Array Conversion Using Offset and Count
Sometimes you have a char[] that contains more data than you need. Maybe you're parsing a fixed-width record from a mainframe, or you've received a buffer that includes headers. The String constructor accepts an offset and count: new String(chars, offset, length). This creates a String using only a subset of the array, starting at offset and taking length characters.
This is not the same as copying the array and then calling substring. The constructor directly reads only the specified range, avoiding an intermediate copy. It's both memory-efficient and faster than the alternative.
Watch out: if offset + count exceeds chars.length, you'll get a StringIndexOutOfBoundsException. Always validate bounds in production code, especially when the offset comes from untrusted input.
Security: Why char[] and How to Zero Out After Conversion
Java's security APIs (JPasswordField, KeyStore, Console.readPassword) return passwords as char[] instead of String for a specific reason: char arrays are mutable and can be cleared explicitly after use. String is immutable â once created, the backing array lives on the heap until garbage collected. And because String objects are interned, they can survive long after you discard the reference.
When you convert a char[] password to a String, you create an immutable copy. Even if you zero the original char[], the String still holds the password in its private array. The only way to truly clear it is to ensure the String object is garbage collected quickly â but you can't zero it.
Best practice: avoid converting char[] to String for sensitive data. If you must, null the String reference right after use and trust the GC (it's not immediate). For additional protection, use a char[] throughout the sensitive operation and only convert at the exact point that requires a String (e.g., passing to a legacy API).
Performance Considerations: When Method Choice Matters
For most use cases, the performance difference between methods is negligible. All four methods ultimately copy the character data. But there are scenarios where method choice impacts memory and CPU.
For small arrays (< 1000 characters), any method works. For large arrays (millions of characters), use the direct constructor or valueOf â both make exactly one copy. StringBuilder.append makes two copies: first into its internal buffer (which may resize), then into the final String. String.copyValueOf is identical to valueOf but kept for legacy compatibility â no performance penalty.
Memory overhead: Each copy consumes 2 bytes per character (char in Java is UTF-16). A 1 million character array â 2 MB for the source + 2 MB for the String = 4 MB peak. StringBuilder adds another 2 MB during append (its buffer may be larger due to growth factor). Avoid StringBuilder for direct conversion.
Consider using the partial constructor (offset+count) when you only need a portion of the array. It avoids copying the entire input.
Java 8 Streams: The Convert-Char-Array-To-String-Using-Streams Trap
Streams are a hammer. Not every problem is a nail. Converting a char[] to String via streams is the kind of code that makes senior devs reach for their coffee and sigh. Why? Because you're boxing each primitive char into a Character object, streaming them, collecting them into an array, then building a String. That's three allocations and a boxing tax for something new String(chars) does in one native call.
If you're already in a stream pipeline and need to join char arrays into a single String, use Collectors.joining() with a StringBuilder internally. But if all you have is a char[], stop showing off. Use the constructor. Streams are for data transformation pipelines, not for reinventing String.valueOf() with extra steps and a memory footprint.
The real production sin? Using streams inside a tight loop. That's how you get GC pauses in your latency-sensitive components. Don't do it. Know your APIs. Pick the right tool.
The copyValueOf() Ghost: Why You Should Never Use This Method in Modern Java
Some methods should have been deprecated years ago. String.copyValueOf() is one of them. It's a relic from Java 1.0 that does exactly what String.valueOf(char[]) does â calls the same String constructor internally. There's zero difference in behavior or performance. It's a redundant method that only exists because the original API designers didn't clean up after themselves.
Why does this matter? Because I've seen codebases where junior devs use copyValueOf() because "it sounds more explicit." It's not. It's confusing. Anyone maintaining your code has to stop and think: "Is this doing something special with defensive copying?" The answer is no. It's a copy of valueOf() in name only.
If you find this in a code review, flag it. Replace it with new String(chars) or String.valueOf(chars). Less cognitive overhead. Consistent with the rest of the codebase. And it signals you understand Java's evolution rather than blindly cargo-culting 90s APIs.
String.copyValueOf() is pure redundancy. Never use it. Replace with String.valueOf() or the constructor.1. Overview
Converting a Java char array to a String seems trivial, yet it's a gateway to understanding how Java manages memory, security, and performance. At its core, the operation must translate a mutable, indexed sequence of 16-bit Unicode characters into an immutable, interned-capable String object. The Java platform offers multiple roads to this destination: the String constructor, String.valueOf(), StringBuilder, and legacy methods like copyValueOf(). Each path carries distinct runtime characteristics, memory implications, and security semantics. A senior engineer selects not the first method that comes to mind, but the one that best fits the contextâwhether that's minimizing object allocations in a hot loop, preventing sensitive data from lingering in the heap, or safely carving a substring from a buffer. This section lays the foundation by framing the conversion as a design decision, not a mechanical step.
3. String.valueOf()
String.valueOf(char[]) is often the recommended choice for converting a character array to a String. Under the hood, it simply calls the String constructor that accepts a char array, making it functionally equivalent to new String(charArray). Why prefer valueOf over the constructor? Primarily for semantic clarity and type safety: the method name communicates intent ('give me the string representation'), and it avoids the visual noise of a constructor call. Furthermore, valueOf is null-safe for other primitive overloads (like valueOf(int)), but for char arrays, passing null will throw a NullPointerExceptionâidentical behavior to the constructor. In performance-critical code, both generate identical bytecode after inlining. The real advantage shines when dealing with method references or lambda expressions, where String::valueOf reads more naturally than a constructor reference. Use valueOf when you want self-documenting code that signals 'convert this to a String' rather than 'create a new String instance'.
String.valueOf() for clarity; it's bytecode-identical to the constructor but communicates purpose better.7. Conclusion
Converting a Java char[] to a String is far more than a syntactic exerciseâit's a decision that impacts security, performance, and code maintainability. From the mutable safety of char arrays for sensitive data (and the imperative to zero them after use), to the subtle bytecode equivalence of String.valueOf() and the constructor, each approach has its role. Avoid legacy pitfalls like copyValueOf() and resist the temptation of Java 8 Streams for this trivial operation. When you need partial conversions, the offset-and-count overloads of both the constructor and valueOf give precise control without creating intermediate arrays. The best practice, distilled: use String.valueOf() for clarity, zero char arrays containing secrets, and never sacrifice correctness for micro-optimizations. Your choice tells a story about your understanding of Java's memory model and your commitment to secure, performant code. Choose wisely.
The Password Leak That Could Have Been Avoided
- Zeroing a char[] after conversion to String does NOT protect the copy inside the String object.
- If you must convert a password char[] to String, minimise its lifetime and null the reference immediately after use.
- Prefer char[] for sensitive data throughout the entire code path when possible.
System.out.println("chars == null: " + (chars == null));String s = chars == null ? null : new String(chars);Key takeaways
String.copyValueOf() is a legacy alias for valueOfCommon mistakes to avoid
5 patternsUsing Arrays.toString(chars) for conversion
Not zeroing the char array after converting a password
Calling String.valueOf(null) expecting null back
Using StringBuilder.append(chars) for a single array conversion
Forgetting to validate offset and count in partial conversion
IllegalArgumentException();Interview Questions on This Topic
What are the different ways to convert a char array to a String in Java?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
That's Strings. Mark it forged?
7 min read · try the examples if you haven't