Java Variables
A variable in Java is a named piece of memory that holds a value your program can read and change while it runs. Variables are the foundation of every Java program: they store numbers, text, true/false flags, and references to objects, and they let you give meaningful names to data instead of working with raw values scattered through your code. Because Java is a statically typed language, every variable has a fixed type that the compiler checks before your program ever runs, which catches a huge class of bugs early.
Overview: How Variables Work
A variable declaration tells the compiler three things at once: the type of data it will hold, the name (identifier) you’ll use to refer to it, and (optionally) an initial value. Once declared, a variable reserves a slot of memory sized to fit its type. An int reserves 4 bytes, a double reserves 8 bytes, a boolean reserves a small flag, and so on.
Java has two broad categories of variables:
- Primitive variables (
int,double,char,boolean, etc.) store the actual value directly in the variable’s memory slot. - Reference variables (
String, arrays, and any object type) do not store the object itself. They store a reference (essentially an address) that points to the actual object, which lives elsewhere in memory (the heap). The variable slot itself is just a small pointer-sized value.
When your program runs, the JVM allocates a stack frame for each method call. Local variables declared inside that method live in slots inside that frame. Primitive values sit directly in their slot; reference variables hold an address that points into the heap, where objects created with new (or string/array literals) actually live. When a method returns, its entire stack frame — and every local variable in it — disappears. Any heap objects that are no longer referenced by anything become eligible for automatic cleanup by the garbage collector.
This distinction matters for behavior, too: because primitives store their value directly, passing an int to a method passes a copy of that number — changes inside the method never affect the caller’s variable. Reference variables also pass by value, but the value being copied is the reference itself, so both the caller and the method point at the same object (though reassigning the parameter inside the method still doesn’t affect the caller’s variable).
Syntax
dataType variableName; // declaration only
dataType variableName = value; // declaration + initialization
variableName = newValue; // assignment (variable must already exist)
final dataType CONSTANT_NAME = value; // a constant that cannot be reassigned
| Part | Meaning |
|---|---|
dataType |
The variable’s type: a primitive (int, double, char, boolean…) or a reference type (String, an array, a class). |
variableName |
The identifier used to refer to the variable elsewhere in the code. |
= |
The assignment operator. It stores the value on the right into the variable on the left. |
value |
A literal, expression, or another variable whose value initializes or updates the variable. |
; |
Every statement, including declarations, ends with a semicolon. |
Naming Rules
- Names may contain letters, digits, underscores (
_), and dollar signs ($), but cannot start with a digit. - Names are case-sensitive:
totalandTotalare different variables. - You cannot use a reserved keyword (
class,int,for,return, etc.) as a variable name. - Convention: use camelCase for variables (
totalPrice,isReady) and UPPER_SNAKE_CASE for constants declared withfinal(MAX_SIZE).
The Eight Primitive Types
| Type | Size | Holds | Default |
|---|---|---|---|
byte |
8 bits | Whole numbers -128 to 127 | 0 |
short |
16 bits | Whole numbers -32,768 to 32,767 | 0 |
int |
32 bits | Whole numbers (~±2.1 billion) | 0 |
long |
64 bits | Very large whole numbers (needs an L suffix for literals) |
0L |
float |
32 bits | Decimal numbers (needs an f suffix) |
0.0f |
double |
64 bits | Decimal numbers (the default for decimal literals) | 0.0 |
char |
16 bits | A single Unicode character, in single quotes: 'A' |
‘\u0000’ |
boolean |
1 bit (JVM-dependent) | true or false |
false |
Note that these defaults only apply to fields (instance/class variables). Java never auto-initializes a local variable declared inside a method — you must assign it a value before reading it, or the compiler will refuse to build your program.
Since Java 10 you can also declare a local variable with var instead of writing the type explicitly. The compiler still assigns a fixed type — it just infers it from the initializer, so var count = 0; is exactly the same as int count = 0; once compiled. var only works when the variable is initialized on the same line, and it cannot be used for fields or method parameters.
Examples
Example 1: Declaring and Printing Different Types
public class Main {
public static void main(String[] args) {
int age = 25;
double price = 19.99;
char grade = 'A';
boolean isJavaFun = true;
String name = "Alice";
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Price: " + price);
System.out.println("Grade: " + grade);
System.out.println("Is Java fun? " + isJavaFun);
}
}
Output:
Name: Alice
Age: 25
Price: 19.99
Grade: A
Is Java fun? true
Each variable is declared with a type, a name, and an initial value on one line. When we concatenate a variable with a string using +, Java automatically converts the variable’s value to text — an int, a double, a char, and a boolean are all converted seamlessly.
Example 2: Constants, Reassignment, and Casting
public class Main {
public static void main(String[] args) {
final double BONUS_RATE = 0.25;
double balance = 1000.0;
double bonus = balance * BONUS_RATE;
balance = balance + bonus;
System.out.println("Bonus earned: " + bonus);
System.out.println("New balance: " + balance);
int wholeDollars = (int) balance;
System.out.println("Balance in whole dollars: " + wholeDollars);
balance = 2500.75;
System.out.println("Balance after deposit: " + balance);
}
}
Output:
Bonus earned: 250.0
New balance: 1250.0
Balance in whole dollars: 1250
Balance after deposit: 2500.75
BONUS_RATE is declared final, which means it can be assigned exactly once — attempting to assign it again would be a compile error, which is exactly what you want for a value that should never change. The plain balance variable, on the other hand, is reassigned twice: once through an arithmetic expression and once with a brand-new literal, completely replacing its old value. The line (int) balance is an explicit cast: it truncates the decimal part of the double to produce an int, a narrowing conversion that Java requires you to request explicitly because it can lose information.
Example 3: Scope and Pass-by-Value
public class Main {
public static void main(String[] args) {
var counter = 0;
for (int i = 1; i <= 3; i++) {
int square = i * i;
System.out.println(i + " squared is " + square);
counter++;
}
System.out.println("Loop ran " + counter + " times");
int score = 10;
System.out.println("Before method call: " + score);
addBonus(score);
System.out.println("After method call: " + score);
}
static void addBonus(int value) {
value = value + 100;
System.out.println("Inside method: " + value);
}
}
Output:
1 squared is 1
2 squared is 4
3 squared is 9
Loop ran 3 times
Before method call: 10
Inside method: 110
After method call: 10
The variables i and square are declared inside the for loop’s braces, so they only exist for the duration of that loop — trying to print square after the loop would fail to compile. counter, declared with var outside the loop, survives across iterations. Notice also that calling addBonus(score) does not change score in main: Java copies the value 10 into the method’s local parameter value, so modifying value inside addBonus only affects that local copy.
Under the Hood: What the JVM Does
- When
mainis invoked, the JVM pushes a new stack frame for it, with a fixed number of local variable slots determined at compile time. - Each declaration like
int age = 25;writes the value25directly into that variable’s slot in the frame. - A reference declaration like
String name = "Alice";creates (or reuses, for string literals) the actual"Alice"object on the heap, then stores a reference to it in thenameslot. - Entering a block (like a
forloop body or anifstatement) doesn’t create a new stack frame, but the compiler tracks that block-scoped variables are only valid within it; slots can be reused once a block ends. - When you call a method, the JVM copies each argument’s value into a fresh slot in the callee’s own stack frame — this is why primitives are unaffected by changes made inside the called method.
- When a method returns, its entire stack frame is popped and discarded. Local variables cease to exist. Any heap objects no longer reachable from a live reference become eligible for garbage collection.
Common Mistakes
Mistake 1: Using a Variable Outside Its Scope
for (int i = 0; i < 3; i++) {
int total = i * 10;
}
System.out.println(total); // will not compile: total is out of scope here
total only exists inside the loop body’s braces. Fix it by declaring the variable in the outer scope and updating it inside the loop:
int total = 0;
for (int i = 0; i < 3; i++) {
total = i * 10;
}
System.out.println(total); // works: 20
Mistake 2: Reading a Local Variable Before It’s Initialized
int result;
System.out.println(result); // will not compile: variable might not have been initialized
Unlike fields, local variables have no default value. The compiler refuses to compile any code path that reads a local variable before it’s definitely been assigned. The fix is simple — always give it a value first:
int result = 0;
System.out.println(result); // works: 0
Mistake 3: Trying to Reassign a final Variable
final int MAX_ATTEMPTS = 3;
MAX_ATTEMPTS = 5; // will not compile: cannot assign a value to final variable
final means “assign once, never again.” If the value genuinely needs to change, it shouldn’t be final in the first place — use a regular variable, or introduce a second variable for the new value.
Best Practices
- Use descriptive, intention-revealing names (
itemCount, notxorc). - Declare variables in the smallest scope that needs them — don’t hoist a loop-only variable up to method level “just in case.”
- Mark values that should never change as
final, especially named constants like tax rates or limits — the compiler will catch accidental reassignment for you. - Initialize a variable at the point of declaration whenever the value is already known, instead of declaring it empty and assigning later.
- Avoid “magic numbers” scattered through your code — give them a named
finalconstant instead. - Use
varonly when the inferred type is obvious from the right-hand side; ifvar result = compute();hides what typeresultis, spell out the type instead. - Choose the narrowest numeric type that comfortably fits your data’s range, but don’t over-optimize —
intanddoubleare fine defaults for most everyday values. - One variable per declaration line —
int width, height;compiles, but separate lines are easier to read and document.
Practice Exercises
- Declare
doublevariables for a rectangle’swidthandheight, compute the area and perimeter into their own variables, and print all four values with labels. - The following code fails to compile:
{ int i = 0; if (true) { int doubled = i * 2; } System.out.println(doubled); }. Explain why, then rewrite it so it compiles and prints0. - Write a program with a
final doubleconstant namedTAX_RATEset to0.2, adouble subtotalof50.0, and print the computed tax and total (subtotal + tax). Expected output:Tax: 10.0andTotal: 60.0.
Summary
- A variable has a type, a name, and a value; Java checks types at compile time.
- Primitive variables store their value directly; reference variables store an address pointing to an object on the heap.
- Local variables live inside a method’s stack frame and must be explicitly initialized before use — Java gives them no default value.
- A variable’s scope is the block where it’s declared; it stops existing once that block ends.
finalcreates a constant that can be assigned exactly once.- Casting (like
(int) someDouble) explicitly converts between numeric types, and is required for narrowing conversions that can lose data. - Method arguments are always passed by value — for objects, it’s the reference that’s copied, not the object itself.
