A variable is a named box that holds a value of a declared type. That definition sounds trivial and it is, but three things about variables in Java are not: the type is fixed, where you declare one decides how long it lives, and Java has three distinct kinds that behave differently.
Declaring and assigning
A declaration gives the box a type and a name. An assignment puts a value in it. You can do both at once, which you usually should.
int age = 30; // declare and assign together
String name = "Folau";
double balance = 1250.75;
boolean active = true;
int score; // declare now
score = 95; // assign later
score = 100; // reassigning is fine — the old value is gone
The = is an assignment operator, not a claim of equality. Read
score = 95 as "put 95 into score", never as "score equals 95". Comparing for equality is
==, and mixing the two up is a real bug —
Operators covers it.
Two rules the compiler enforces, both worth meeting now:
int count = 5;
// count = "five"; // will not compile: the type is part of the variable, permanently
int total;
// System.out.println(total); // will not compile: variable total might not have been initialized
That second error is Java protecting you from reading a box before anything was put in it. Note that it applies to local variables only — and the next section is why.
The three kinds
Where you declare a variable determines its lifetime, its default value, and who can see it.
public class Account {
static int accountsCreated = 0; // 1. static — one copy, shared by the whole class
private String owner; // 2. instance — one copy per object
private double balance;
public Account(String owner) {
this.owner = owner;
accountsCreated++;
}
public void deposit(double amount) {
double newBalance = balance + amount; // 3. local — exists only in this method
balance = newBalance;
}
}
| Kind | Declared | Lives | Default if unassigned |
|---|---|---|---|
| Local | inside a method or block | until the method returns | none — the compiler makes you assign it |
| Instance | in the class, no static | as long as the object does | 0, false, or null |
| Static | in the class, with static | as long as the program does | 0, false, or null |
Those defaults are a common source of surprise. An instance field you forgot to set is not an
error — it is silently null, and you find out when something calls a method on it. A
local variable you forgot to set is a compile error. The compiler is stricter about locals precisely
because it can be.
Scope: where a variable is visible
A variable exists from its declaration to the closing brace of the block containing it. Outside that block, the name does not exist.
void demonstrate(boolean flag) {
int outer = 1;
if (flag) {
int inner = 2;
System.out.println(outer); // fine — outer is still in scope
}
// System.out.println(inner); // will not compile: cannot find symbol
}
This catches people most often with loops, where the counter is scoped to the loop:
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
// System.out.println(i); // will not compile: i died with the loop
int found = -1; // declare outside if you need it after
for (int i = 0; i < 3; i++) {
if (i == 2) found = i;
}
System.out.println(found); // 2
Keeping scope tight is a habit worth forming early: a variable declared in the smallest block that needs it cannot be accidentally used, reused, or left holding a stale value.
var, and when not to use it
Since Java 10, you can write var for a local variable and let the compiler work out
the type from the right-hand side.
var message = "hello"; // String
var count = 42; // int
var scores = new HashMap<String, Integer>(); // HashMap<String, Integer>
This is not dynamic typing. message is a String
permanently, and assigning an int to it will not compile. You have skipped writing the
type, not skipped having one.
var earns its place when the type is long and already obvious from the right-hand
side — that HashMap line would otherwise repeat itself. It hurts when the right-hand
side is a method call, because the reader now has to go and look up what the method returns:
record Customer(String name) { }
Customer lookup(String id) {
return new Customer("Folau");
}
void call() {
var a = lookup("c-1"); // what is a? You cannot tell without leaving this line.
Customer b = lookup("c-1"); // clearer, and one word longer
}
The rule: use var when it removes noise, not when it removes information.
It only works on locals — never on fields, parameters or return types, where the type is part of a
contract someone else reads.
Naming
Java's conventions are near-universal and every team expects them:
camelCasefor variables and methods —accountBalance, notaccount_balance.UPPER_SNAKE_CASEfor constants —static final int MAX_RETRIES = 3;- Names that say what the thing is.
dcosts the next reader thirty seconds;daysSinceLastLogincosts you nothing.
Loop counters (i, j) are the accepted exception, because their scope is
three lines and the convention is older than Java.
Next
Every variable needed a type, and so far you have seen int, double,
boolean and String without explanation. Data
types is next: the eight primitives, what each one costs, and the two that will surprise you.