JAVA / STRINGS AND TEXT HANDLING
Creating strings and how concatenation really works
Create Java strings from literals, char or byte data and value conversions, and predict exactly what each + expression compiles into and prints.
What you will learn
- Create strings from literals, char arrays and decoded bytes, and know when each fits
- Tell at a glance whether a + expression is folded by javac or built at run time
- Predict mixed number-and-text results from left-to-right grouping of +
- Convert values on purpose: + yields the text null for a null reference, concat throws
Understanding Creating strings and how concatenation really works
Most strings start life as a literal: the characters are stored in the class file's constant pool and the JVM hands you a finished String the first time that line runs. The constructors exist for a different job, turning raw data into text. new String(chars) copies a char array so later edits to the array cannot change the string, and new String(bytes, StandardCharsets.UTF_8) decodes bytes, which is why the charset argument matters: omit it and you get the platform default, so identical code can produce different characters on different machines. For primitives, String.valueOf is the direct conversion, and it is the same conversion the language uses internally.
The + operator is not one mechanism but two, and javac picks between them. If every operand is a compile-time constant, meaning a literal or a final variable initialized from a constant expression, the compiler evaluates the whole thing and writes a single literal into the class file, so "he" + "llo" costs nothing at run time and is even legal where a constant is required, such as a case label. If any operand must be computed, javac emits code that assembles a new String as the line executes: since Java 9 that is one invokedynamic call handled by StringConcatFactory, which measures every operand, allocates the exact array once and fills it, while Java 8 and earlier emitted a chain of StringBuilder.append calls. Either way one expression yields one finished String, no matter how many plus signs it contains.
The rest of the behaviour follows from two rules. The + sign means concatenation only when at least one operand is a String, otherwise it is ordinary arithmetic, and it groups left to right, so 1 + 2 + " items" adds first and gives "3 items" while "total: " + 1 + 2 concatenates twice and gives "total: 12". Non-String operands are converted as if by String.valueOf, which is why a null reference becomes the four characters null instead of throwing, and why any object shows up through its toString. char is a numeric type, so 'B' + 1 is 67 until a String joins the expression and forces text conversion.
public class MakingStrings {
static final String PREFIX = "user";
public static void main(String[] args) {
String literal = "hello";
String fromChars = new String(new char[] {'h', 'e', 'l', 'l', 'o'});
String fromNumber = String.valueOf(42);
System.out.println(literal + " " + fromChars + " " + fromNumber);
String folded = "he" + "llo"; // javac stores one literal
String id = PREFIX + "-" + 7; // assembled when this line runs
System.out.println(folded.length() + " " + id);
System.out.println(1 + 2 + " items");
System.out.println("total: " + 1 + 2);
String missing = null;
System.out.println("value=" + missing);
char grade = 'B';
System.out.println(grade + 1);
System.out.println("" + grade + 1);
}
}Every + expression is a single string-building step that javac either folds into one literal or turns into one run-time concatenation, after converting each non-String operand through String.valueOf.
Worked examples
Folded at compile time versus built at run time
Shows that only a concatenation of compile-time constants can stand in for a literal.
public class Folding {
static final String GREETING = "he" + "llo";
public static void main(String[] args) {
String input = args.length > 0 ? args[0] : "hello";
switch (input) {
case GREETING -> System.out.println("matched a compile-time constant");
default -> System.out.println("no match");
}
String part = "he";
String atRuntime = part + "llo";
System.out.println(atRuntime.length() + " " + atRuntime.equals(GREETING));
}
}Example explained
Line 1Both operands of "he" + "llo" are literals, so javac evaluates it and stores the single constant hello in the class file.
Line 2case GREETING compiles only because GREETING is a constant variable; a case label rejects anything that has to be computed while the program runs.
Line 3part is an ordinary local variable, so part + "llo" becomes a run-time concatenation even though the resulting characters are identical.
Line 4equals looks at characters, not at how the string was produced, so both routes report true.
How operands become text
Demonstrates that + converts every non-String operand through String.valueOf, while concat dereferences its argument.
import java.util.List;
public class Converting {
record Point(int x, int y) {}
public static void main(String[] args) {
System.out.println("point " + new Point(2, 3));
System.out.println("list " + List.of(1, 2));
System.out.println("char " + 'x' + " int " + 7 + " double " + 1.0);
String missing = null;
System.out.println("name " + missing);
try {
"prefix-".concat(missing);
} catch (NullPointerException e) {
System.out.println("concat threw NullPointerException");
}
}
}Example explained
Line 1The Point operand is passed through String.valueOf, which calls the record's generated toString, giving Point[x=2, y=3].
Line 2The list prints as [1, 2] for the same reason: + never inspects the object, it only asks it for text.
Line 31.0 becomes the three characters 1.0 because the conversion uses Double.toString, which always keeps a decimal part.
Line 4A null String operand of + turns into the text null, but concat calls isEmpty on its argument first, so it throws instead.
Important notes
Folding is a property of the expression, not of the characters: replace a literal with a method call or a non-final variable and the same text is now assembled at run time.
The bytecode for + changed in Java 9, so javap now shows invokedynamic where older write-ups show StringBuilder; the observable result is the same.
Common mistakes
Writing "total: " + a + b with a = 2 and b = 3 and getting total: 23, because the leftmost + already turned the result into a String.
Adding characters, as in 'A' + 'B', which prints 131: char stays numeric until a String enters the expression.
Calling String.valueOf(null), which resolves to the char[] overload and throws NullPointerException at run time; String.valueOf((Object) null) returns null as text.
Try it yourself
Change, predict, then run
In a scratch class print "n=" + 1 + 2 and "n=" + (1 + 2) on separate lines, then print 'a' + 'b' and "" + 'a' + 'b'. Add a comment on each line saying whether the + was arithmetic or concatenation and why.
Open the Java workspaceCheck your understanding
n is an int and suffix is a String, both read from input. On Java 17, what happens when String s = "id-" + n + "-" + suffix; executes?
- Three concatenations, each producing an intermediate String that is immediately discarded
- Nothing at run time, because javac folds the whole expression into one literal
- One concatenation that sizes the result once and fills it in a single pass
- One StringBuilder per + operator, so three builders and three toString calls
Show answer
javac compiles the entire expression into a single invokedynamic call to StringConcatFactory, which measures all four pieces, allocates the exact array once and copies into it, so no intermediate strings exist. The first option describes what several separate s += ... statements would do; folding is impossible here because n and suffix are not compile-time constants.