Junior 3 min · March 30, 2026

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.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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
Plain-English First

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.

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.

CharArrayConversionExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package io.thecodeforge.strings;

import java.util.Arrays;

public class CharArrayConversionExample {

    public static void main(String[] args) {
        char[] chars = {'T', 'h', 'e', 'C', 'o', 'd', 'e', 'F', 'o', 'r', 'g', 'e'};

        // Method 1: String constructor — most common and idiomatic
        String s1 = new String(chars);
        System.out.println("Constructor: " + s1);

        // Method 2: String.valueOf() — same implementation, cleaner intent
        String s2 = String.valueOf(chars);
        System.out.println("valueOf:     " + s2);

        // Method 3: StringBuilder append — useful when building incrementally
        StringBuilder sb = new StringBuilder();
        sb.append(chars);
        String s3 = sb.toString();
        System.out.println("StringBuilder: " + s3);

        // Method 4: String.copyValueOf() — explicit copy semantics
        String s4 = String.copyValueOf(chars);
        System.out.println("copyValueOf: " + s4);

        // Partial range conversion — constructor with offset and count
        String partial = new String(chars, 3, 8); // offset=3, count=8
        System.out.println("Partial [3,8]: " + partial); // CodeForg

        // Security use case: zero out the char array after conversion
        char[] password = {'s', 'e', 'c', 'r', 'e', 't'};
        String passwordStr = new String(password);
        Arrays.fill(password, '\0');  // Overwrite sensitive data
        System.out.println("Password zeroed: " + Arrays.toString(password));
    }
}
Output
Constructor: TheCodeForge
valueOf: TheCodeForge
StringBuilder: TheCodeForge
copyValueOf: TheCodeForge
Partial [3,8]: CodeForg
Password zeroed: [\0, \0, \0, \0, \0, \0]
The Copy Is Not the Original
  • 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.
Production Insight
You might think StringBuilder.append(char[]) creates an intermediate copy into StringBuilder's internal buffer — it does, but that's actually a second copy.
The String constructor already copies the char[] once. Using StringBuilder adds another allocation and copy, doubling memory overhead for large arrays.
Rule: for direct char[] → String, use the constructor or valueOf. Reserve StringBuilder for incremental builds.
Key Takeaway
new String(chars) and String.valueOf(chars) share the same implementation under the hood.
String.valueOf(null) returns "null" string, new String(null) throws NPE.
Use valueOf when null-safety with a fallback token is acceptable; use constructor when null should fail fast.

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.

NullHandlingExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package io.thecodeforge.strings;

public class NullHandlingExample {
    public static void main(String[] args) {
        char[] nullArray = null;

        // Throws NullPointerException
        // String s1 = new String(nullArray);

        // Returns "null" string
        String s2 = String.valueOf(nullArray);
        System.out.println("valueOf(null): '" + s2 + "'");

        // Check length — it's 4, not 0
        System.out.println("Length: " + s2.length());

        // If you need null propagation:
        String s3 = nullArray == null ? null : new String(nullArray);
        System.out.println("Ternary result: " + s3);
    }
}
Output
valueOf(null): 'null'
Length: 4
Ternary result: null
Production Insight
We had a service that validated credit card numbers. The upstream returned a char[] but sometimes null for invalid requests. The developer used String.valueOf(chars) and downstream checked for null. The string "null" passed validation (character matching) and caused cryptic failures.
The fix: explicit null check before conversion, or catch the NPE from new String() and handle null separately.
Lesson: String.valueOf(null) does not propagate null. It creates a "null" token. Document this behaviour if your code relies on it.
Key Takeaway
String.valueOf(char[]) internally calls new String(char[]) — identical performance.
Null handling is the only behavioural difference.
Choose based on whether incoming null is a valid state or an error condition.
Which Conversion Method to Use?
Ifchar[] is guaranteed non-null
UseUse new String(chars) — fail fast on unexpected null, no ambiguity
Ifchar[] could be null, and you need a safe string representation
UseUse String.valueOf(chars) — returns "null" string, avoid NPE
IfYou need null to propagate as null
UseAdd explicit check: chars == null ? null : new String(chars)
IfYou are building a string incrementally from multiple char[] sources
UseUse StringBuilder.append(chars) — but be aware of double copy
IfYou are working in legacy code that uses String.copyValueOf()
UseKeep it for consistency — but it's identical to valueOf and deprecated in spirit

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.

PartialConversionExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.thecodeforge.strings;

public class PartialConversionExample {
    public static void main(String[] args) {
        char[] buffer = {'S', 'T', 'A', 'R', 'T', 't', 'h', 'e', 'C', 'o', 'd', 'e', 'F', 'o', 'r', 'g', 'e', 'E', 'N', 'D'};
        int headerSize = 5; // 'START'
        int trailerSize = 3; // 'END'
        int dataOffset = headerSize;
        int dataLength = buffer.length - headerSize - trailerSize;

        // Convert only the data portion
        String data = new String(buffer, dataOffset, dataLength);
        System.out.println("Data portion: " + data); // theCodeForge

        // Unsafe — this throws StringIndexOutOfBoundsException
        // String bad = new String(buffer, 0, 100);

        // Safe conversion with validation
        int offset = 0;
        int length = 100;
        if (offset >= 0 && length >= 0 && offset + length <= buffer.length) {
            String safe = new String(buffer, offset, length);
        } else {
            throw new IllegalArgumentException("Invalid offset/length");
        }
    }
}
Output
Data portion: theCodeForge
Production Insight
In a file parser processing millions of records, using substring on a full copy of the char[] created O(n^2) memory overhead. Switching to new String(char[], offset, count) eliminated the extra copy and cut GC pressure by 30%.
The bounds check is cheap but critical. In high-throughput code, the JVM may inline the check. Still, always validate offset and count when they come from external input.
Rule: use partial constructor whenever your data lives in a larger buffer — avoid copying the whole buffer just to discard parts.
Key Takeaway
new String(chars, offset, count) creates a string from a subrange without extra copy.
Always validate bounds in production code to avoid StringIndexOutOfBoundsException.
Prefer this over copying the full array and calling substring for large buffers.

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).

SecurePasswordConversion.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.thecodeforge.security;

import java.util.Arrays;

public class SecurePasswordConversion {
    public static void main(String[] args) {
        char[] password = retrievePasswordFromSecureStore();

        // Only convert when absolutely necessary
        String passwordString = null;
        try {
            passwordString = new String(password);
            authenticate(passwordString);
        } finally {
            // Zero original char array
            Arrays.fill(password, '\0');
            // Nullify the String reference to help GC
            passwordString = null;
        }
    }

    private static char[] retrievePasswordFromSecureStore() {
        // Simulating a secure store
        return new char[]{'s', '3', 'c', 'r', '3', 't'};
    }

    private static void authenticate(String password) {
        System.out.println("Authenticating with: " + password);
    }
}
Output
Authenticating with: s3cr3t
(Original char[] zeroed, String reference nulled)
Production Insight
A major e-commerce platform had a security audit that found customer passwords in a heap dump taken for performance analysis. The passwords were stored as char[] in memory but had been converted to String for logging purposes. The char[] was zeroed after conversion, but the String objects persisted in the heap dump.
The fix: remove the conversion entirely and pass char[] directly to the authentication API (which accepted char[]). Also, disable heap dumps on production unless absolutely necessary and ensure they are immediately destroyed.
Lesson: zeroing the source char[] does not protect the copy inside the String. Avoid converting sensitive data to String if possible.
Key Takeaway
Zeroing a char[] after conversion to String does NOT clear the String's internal copy.
Null the String reference after use to help GC, but understand the copy still exists temporarily.
Prefer char[] for sensitive data end-to-end — only convert when forced by legacy APIs.

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.

StringBuilder Overhead for Direct Conversion
Never use StringBuilder.append(char[]) as the primary method for converting a single char[] to String. It creates two copies of the data. Use new String(chars) or String.valueOf(chars) instead. StringBuilder is only beneficial when you're appending multiple char[] or other values incrementally.
Production Insight
A log aggregation service processed char[] buffers containing up to 500,000 characters per event. The original code used StringBuilder.append(chars).toString(), causing 2x memory per conversion. With 10,000 concurrent requests, memory spiked to 20 GB for the conversion alone. Switching to new String(chars) cut memory usage in half and eliminated GC pressure.
Rule: for single-array conversion, always prefer the String constructor or valueOf. StringBuilder is for building strings, not direct conversion.
Key Takeaway
new String(chars) and String.valueOf(chars) are equivalent in performance: one copy.
StringBuilder.append(chars) makes two copies — avoid for direct conversion.
Use partial constructor for subrange conversion — no extra copy needed.
● Production incidentPOST-MORTEMseverity: high

The Password Leak That Could Have Been Avoided

Symptom
Heap dump from a production JVM revealed plaintext passwords in String objects that should have been ephemeral char[] values.
Assumption
The team assumed that converting char[] to String with new String(password) was safe because the char[] was zeroed immediately after conversion.
Root cause
The char[] was zeroed, but the String constructor copies the characters into a new internal array. That String object persisted in the heap until GC. The zeroed char[] did nothing to the copy inside the String. A heap dump captured the String with the password intact.
Fix
Avoid converting sensitive char[] data to String if possible. If conversion is unavoidable, use a minimal scope and explicitly null the String reference after use. For production, consider using a SecretKey or a dedicated secure byte array wrapper.
Key lesson
  • 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.
Production debug guideSymptom → Action matrix for common conversion pitfalls in production5 entries
Symptom · 01
NullPointerException when calling new String(chars)
Fix
Check if chars is null. Use String.valueOf(chars) instead — it returns "null" string rather than throwing NPE. If you need null propagation, add explicit null check before conversion.
Symptom · 02
String contains unexpected literal "null" text
Fix
Verify that the char[] variable was not null. String.valueOf(null) returns "null" as a string. Use a ternary: chars == null ? null : new String(chars).
Symptom · 03
String output shows brackets and commas like [a, b, c]
Fix
You called Arrays.toString(chars) instead of new String(chars). Replace with new String(chars) or String.valueOf(chars).
Symptom · 04
OutOfMemoryError when converting a very large char[]
Fix
The String constructor creates an exact copy of the char[]. For large arrays (e.g., >10 MB), consider processing in chunks or using StringBuilder with incremental append to reduce peak memory.
Symptom · 05
Partial conversion produces wrong output
Fix
Check offset and count parameters in new String(chars, offset, count). Ensure offset + count ≤ chars.length. The result will only include characters from offset to offset+count-1.
★ char[] to String Quick Debug Cheat SheetFive common symptoms and immediate commands to diagnose char[] to String conversion issues in production.
String contains "null" text
Immediate action
Check if char[] is null
Commands
System.out.println("chars == null: " + (chars == null));
String s = chars == null ? null : new String(chars);
Fix now
Replace String.valueOf(chars) with explicit null check
Array converted to bracketed text [a, b]+
Immediate action
Identify the wrong method
Commands
System.out.println(chars.getClass().getName()); // [C
String s = new String(chars); // correct
Fix now
Search code for Arrays.toString(chars) and replace
OutOfMemoryError during conversion+
Immediate action
Check size of char[]
Commands
System.out.println("Char array length: " + chars.length);
If > 10 million, consider incremental StringBuilder with append(char[], int, int)
Fix now
Use StringBuilder.append(char[], offset, count) in a loop
Password appears in heap dump+
Immediate action
Find all char[] to String conversion points
Commands
jcmd <pid> GC.heap_dump /tmp/dump.hprof
Analyze dump with Eclipse MAT, search for password field strings
Fix now
Re-architect: avoid String for passwords; use char[] with explicit zeroing
Partial conversion produces wrong length String+
Immediate action
Verify offset and count boundaries
Commands
System.out.println("Offset=" + offset + ", count=" + count + ", length=" + chars.length);
Ensure offset + count <= chars.length
Fix now
Add precondition check: if (offset < 0 || count < 0 || offset + count > chars.length) throw new IllegalArgumentException();
char[] to String Conversion Methods Comparison
MethodSyntaxNull-safe?Use WhenCopies
String constructornew String(chars)NPE on nullDefault choice — clear intent, fail fast on null1 copy
String.valueOf()String.valueOf(chars)Returns 'null' stringNull safety matters, safe fallback token1 copy
StringBuilder.append()sb.append(chars).toString()NoBuilding incrementally from multiple sources2 copies
String.copyValueOf()String.copyValueOf(chars)NPE on nullLegacy code compatibility, explicit copy semantics1 copy

Key takeaways

1
new String(chars) and String.valueOf(chars) are the two most common approaches
both produce identical results for non-null arrays.
2
String.valueOf(null) returns the string 'null' rather than null
use explicit null check if null propagation matters.
3
new String(chars, offset, count) handles partial array conversion
useful when your char[] contains more data than you want.
4
Always zero out char[] password arrays with Arrays.fill(password, '\0') after use
this is why security APIs return char[] instead of String.
5
Prefer direct constructor or valueOf over StringBuilder for single char[] to String conversion
avoids double memory copy.
6
String.copyValueOf() is a legacy alias for valueOf
no performance or behavioural difference in modern Java.

Common mistakes to avoid

5 patterns
×

Using Arrays.toString(chars) for conversion

Symptom
String output shows '[T, h, e, C, o, d, e, F, o, r, g, e]' with brackets and commas — not the concatenated string.
Fix
Replace Arrays.toString(chars) with new String(chars) or String.valueOf(chars).
×

Not zeroing the char array after converting a password

Symptom
Sensitive data remains in memory as a mutable char[] even after use, visible in heap dumps or memory analysis.
Fix
Call Arrays.fill(password, '\0') immediately after converting to String and using the password. Note: this does not clear the String's internal copy.
×

Calling String.valueOf(null) expecting null back

Symptom
Downstream code checks for null but receives the literal string 'null', leading to logic errors or false validation passes.
Fix
If you need null propagation, add explicit null check before conversion: chars == null ? null : new String(chars) or String.valueOf(chars) depending on intent.
×

Using StringBuilder.append(chars) for a single array conversion

Symptom
Unnecessary memory overhead — two copies instead of one. For large arrays, can cause OutOfMemoryError or excessive GC.
Fix
Use new String(chars) or String.valueOf(chars) for direct conversion. Reserve StringBuilder for when you're appending multiple pieces.
×

Forgetting to validate offset and count in partial conversion

Symptom
StringIndexOutOfBoundsException at runtime when offset + count exceeds array length.
Fix
Add precondition checks: if (offset < 0 || count < 0 || offset + count > chars.length) throw new IllegalArgumentException();
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What are the different ways to convert a char array to a String in Java?
Q02SENIOR
Why do some security APIs return char[] instead of String for passwords?
Q03SENIOR
What happens if you pass null to String.valueOf(char[]) vs new String(ch...
Q04SENIOR
Is there any performance difference between the four conversion methods ...
Q05JUNIOR
How do you convert only a portion of a char array to a String?
Q01 of 05JUNIOR

What are the different ways to convert a char array to a String in Java?

ANSWER
The four primary ways are: 1) new String(chars) — most common, throws NPE on null; 2) String.valueOf(chars) — returns 'null' string for null; 3) StringBuilder.append(chars).toString() — useful when building incrementally; 4) String.copyValueOf(chars) — legacy method identical to valueOf. All produce the same string for non-null input. The key differences are null handling and intent signalling.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I convert a char array to a String in Java?
02
Why does Arrays.toString(chars) not work for char array to String conversion?
03
Can I convert a char array to String without copying the data?
04
Is String.copyValueOf() deprecated?
05
Does zeroing a char[] after conversion protect the password in memory?
🔥

That's Strings. Mark it forged?

3 min read · try the examples if you haven't

Previous
Character Class in Java
11 / 15 · Strings
Next
List to Comma Separated String in Java