Default values, literals and constants
Who gets a default value and who doesn't, every literal form in modern Java, and how static final constants get copied into other classes.
On this page
🧱 What are default values, literals and constants?
A default value is what a variable holds before you assign anything: fields and array elements start at 0, false or null, while local variables have no default at all and won't compile until you assign them. A literal is a value written directly in source, like 42, 0xFF, 3.5f or '\n'. A constant is a final variable that can't be reassigned; when it's static final and initialised with a compile-time value like MAX_RETRIES = 3, the compiler copies 3 into every class that uses it.
For this lesson, we'll focus on the rules that decide when Java fills in a value for you, every literal form you'll meet in real code, and how final and static final constants behave, including one constant-inlining pitfall that has broken real deployments. Interviewers use this topic to test precision: "what's the default value of a local int?" has a wrong answer that sounds right.
Below the line (out of scope)
- Initialisation order of static and instance blocks (Core Java module)
enumas a richer alternative to integer constants (Core Java module)- Immutability of objects referenced by
finalfields beyond the basics
The 30-second answer
Fields and array elements get defaults: 0 for numbers, '\u0000' for char, false for boolean and null for references. Local variables get none: the compiler's definite-assignment check rejects any read before a write. Literals come in decimal, hex (0x), binary (0b, Java 7), octal (leading 0), with _ separators, L/F/D suffixes and, since Java 15, text blocks. final means "assigned once". A static final primitive or String initialised with a constant expression is a compile-time constant, and javac inlines its value into other classes, so changing it without recompiling those classes leaves them using the old value.
What you’ll learn
- Why fields and array elements get default values while local variables must be definitely assigned
- Every literal form: underscores, hex, binary, octal,
L/F/Dsuffixes,charescapes and text blocks (Java 15) - What
finalreally freezes, and the naming rules for constants - How compile-time constants are inlined across classes, and the deployment bug that causes
01Picture it first: who gets a default?
Think of the JVM as a landlord. When it hands you a new object or array, it cleans the room first: the memory is zeroed, so every field and element reads as 0, false or null. A local variable is different: it's a scratch slot in a stack frame that's reused from call to call, and the compiler, not the JVM, makes sure you never read it before writing it.
Fields and array elements are zeroed by the JVM and get default values; local variables get no default and must be definitely assigned before use
| Type | Default for fields and array elements |
|---|---|
byte, short, int |
0 |
long |
0L |
float |
0.0f |
double |
0.0d |
char |
'\u0000' (the NUL character, numeric value 0) |
boolean |
false |
any reference (String, Integer, arrays, ...) |
null |
02Fields vs locals: definite assignment
Try it yourself first
Before reading on: which line fails to compile, and what does the rest print?
class Counter {
int hits; // field
Integer boxedHits; // field, reference type
boolean enabled; // field
void report() {
int local;
System.out.println(hits + " " + boxedHits + " " + enabled);
int[] buckets = new int[3];
System.out.println(buckets[0]);
System.out.println(local); // ?
}
}
The last line doesn't compile: variable local might not have been initialized. Remove it and you get 0 null false, then 0.
Notice three things:
hitsis0, butboxedHitsisnull, not0. A wrapper field is a reference, and unboxing it (boxedHits + 1) throwsNullPointerException.- The array elements are zeroed even though
bucketsis a local. The array object lives on the heap and gets cleaned; only the local reference needs assigning. - The local
inthas no value at all. This isn't a runtime check:javacruns a flow analysis called definite assignment (JLS chapter 16) and refuses any path where a read could happen before a write.
Definite assignment is smarter than "assigned on the line it's declared":
int status;
if (ok) {
status = 200;
} else {
status = 500;
}
System.out.println(status); // fine: assigned on every path
int retries;
if (ok) retries = 3;
System.out.println(retries); // compile error: not assigned when !ok
Don't "fix" this error by writing int status = 0; out of habit. A dummy default hides the bug the compiler was trying to show you. Assign on every branch, or use a switch expression that must produce a value.
03Literals: every form you'll meet
Try it yourself first
Predict these before reading the answers in the comments.
int million = 1_000_000; // underscores (Java 7) are ignored
int mask = 0xFF; // hex: 255
int flags = 0b1010_0001; // binary (Java 7): 161
int perms = 0755; // octal! 493, not 755
long big = 9_000_000_000L; // needs L: too big for int
float ratio = 0.75f; // needs f: 0.75 alone is a double
double d = 1e-3; // 0.001, the D suffix is optional
char letter = 'A'; // also '\u0041' or (char) 65
char tab = '\t'; // escapes: \t \n \r \b \f \s \' \" \\ and \0..\377 octal
A few rules hide in there:
- Integer literals are
intunless they end inL.long big = 9000000000;doesn't compile: integer number too large. Use a capitalL; a lowercasellooks like1. - Decimal literals with a point or exponent are
double.float f = 1.5;is a compile error, becausedouble→floatis narrowing. Addf. - A leading
0means octal.010is 8 and08doesn't compile. This catches people formatting codes like0755or zero-padded IDs. - Underscores must sit between digits:
1_000is fine,_1000,1000_and0x_FFaren't. \s(a single space) was added with text blocks in Java 15.
Text blocks (Java 15)
A text block is a multi-line String literal between triple quotes. Java removes the common leading indentation (measured from the least-indented line, including the closing """), normalises line endings to \n, and keeps the rest exactly as written.
String json = """
{
"name": "atlas",
"tags": ["java"]
}
""";
That gives you the JSON with the four-space margin stripped and a trailing newline (because the closing """ is on its own line). End a line with \ to join it with the next one, and use \s to keep trailing spaces that would otherwise be stripped. A text block is still a plain String constant, so it's interned and can be a compile-time constant like any other literal.
04final and static final
final means assigned exactly once. It applies to three things:
| Where | What final means |
|---|---|
| Local variable | Can't be reassigned. Can be assigned later ("blank final") as long as it's exactly once on every path. |
| Instance field | Must be assigned exactly once by the end of every constructor (or in its declaration or an instance initialiser). The compiler stops you reading it before that, although a method called from the constructor can still see the default. |
static field |
Must be assigned exactly once in its declaration or a static { } block. |
final freezes the variable, not the object it points to. final List<String> names = new ArrayList<>(); can't be pointed at another list, but names.add("x") works fine. For a read-only collection, use List.of(...) or Collections.unmodifiableList(...).
public final class HttpLimits {
public static final int MAX_RETRIES = 3; // compile-time constant
public static final String USER_AGENT = "atlas/1.0"; // compile-time constant
public static final long TIMEOUT_MS = 30 * 1_000L; // still constant: constant expression
public static final Integer BOXED = 3; // NOT a compile-time constant (wrapper)
public static final int FROM_ENV = Integer.getInteger("retries", 3); // NOT constant: runtime call
public static final List<String> HOSTS = List.of("a", "b"); // NOT constant: object
private HttpLimits() {}
}
Naming convention: constants are UPPER_SNAKE_CASE, and in practice that means static final fields whose value is deeply immutable (primitives, String, immutable collections). A static final StringBuilder is not a constant in spirit, so name it like a normal field. Ordinary final locals and fields keep camelCase.
05The constant-inlining pitfall
A compile-time constant (JLS §4.12.4) is a final variable of primitive type or String, initialised with a constant expression. For these, javac doesn't emit a field read in the classes that use them. It copies the value into their bytecode.
Try it yourself first
Client prints HttpLimits.MAX_RETRIES. You change MAX_RETRIES from 3 to 5, recompile only HttpLimits.java, and redeploy that one class. What does Client print?
It still prints 3. Run javap -c Client and you'll see iconst_3, not getstatic HttpLimits.MAX_RETRIES: the number was baked in when Client was compiled. In a quick test, BOXED and FROM_ENV do pick up the new value, because they're real field reads.
This bites in two places. First, shared constants jars: team A bumps a public static final int in a library, team B upgrades the jar without recompiling, and B's code silently keeps the old value. Second, incremental builds and hot-swap tools that only recompile changed files. If a public constant might change, expose it through a method (static int maxRetries()), or make it non-constant on purpose. Inlining is fine for values that truly never change, like SECONDS_PER_MINUTE = 60.
Many candidates are quick to say "static final makes it a constant, so it's inlined". It's frequently a mistake because inlining depends on the initialiser and the type, not the modifiers alone: static final Integer, static final int X = compute() and static final List<...> are all real field reads. Saying that distinction precisely is what makes this answer sound senior.
There's one more side effect: reading a compile-time constant from another class doesn't trigger that class's static initialisation, because no getstatic instruction runs. Reading BOXED does.
06How to say it in the interview
When I ask about defaults, I'm listening for whether you separate fields and arrays from locals without hesitating, and whether you know the local rule is enforced by the compiler. On constants, I'm listening for the word "inlined" and the deployment risk that comes with it.
In the interview
"Fields and array elements are zeroed by the JVM, so they default to 0, false or null. Locals have no default: the compiler's definite-assignment analysis rejects any read before a write. final means assigned once, and it applies to the variable, not the object. A static final primitive or String with a constant initialiser is a compile-time constant, so javac inlines its value into every class that uses it. That's why changing a public constant without recompiling its users leaves them on the old value, so for values that might change I expose them through a method."
Here's how this might look if you sketch it on the whiteboard:
On the whiteboard
Defaults & constants
- fields, array elements: 0 / false / '\u0000' / null
- locals: none → definite assignment (compile error)
- 1_000 · 0xFF · 0b1010 · 010 = 8 · 9L · 1.5f · """text"""
- final = assign once (variable, not object)
- compile-time constant: final + primitive/String + constant expr
- inlined into callers → recompile users when it changes
Further reading (7)
- Java Language Specification §4.12.5: Initial Values of Variables
- Java Language Specification §4.12.4: final Variables (constant variables)
- Java Language Specification Chapter 16: Definite Assignment
- Java Language Specification §3.10: Literals
- Java Language Specification §13.1: The Form of a Binary (constant inlining)
- JEP 378: Text Blocks
- Programmer's Guide to Text Blocks