First Steps
Variables & Primitive Types
Java is statically typed: every variable gets a type when you declare it, and the compiler checks that type on every line. The eight primitives hold their values directly. Covers declarations, names, definite assignment, final, exact ranges, literals and their types, and var (inferred, not dynamic), with every trap shown as a real compiler error.
Suggest an editVariables & Primitive Types — Typed Boxes for Values
A variable is a named place to keep a value, so you can use it again. In Java, that place has one more property, and it shapes everything you write: a type.
- The type says what the variable may hold: a whole number, a decimal, text.
- You fix the type when you declare the variable, and it never changes.
- The compiler checks every line that uses the variable against that type.
Java is statically typed, where static means the type is decided before the program runs. You tell the compiler, "this name holds an int." From then on, it refuses to compile any line that breaks that promise.
💡 The core idea.
- A variable is a named place to keep a value.
- Its type is fixed when you declare it and checked by the compiler on every line.
- Statically typed means the type is decided before the program runs.
The simplest values are the primitives: eight built-in types for numbers, true/false, and single characters. A primitive variable holds its value directly — the box is the number.
That word "directly" will matter later. Java has a second family of variable, the reference, which holds the location of a value rather than the value itself; References, equality and the object model teaches it. For now, just remember that primitives hold their value. Every output below was produced by compiling and running the code on Java 25.
You'll be able to: declare a variable with a type and a legal name, and say why 2nd and class are rejected; fix "might not have been initialized" by giving a variable a value before it's read; choose a primitive type for a value based on the exact ranges; predict from the literal's own type and the variable's type whether a declaration compiles; explain why a var variable still has one fixed type, and name where var isn't allowed.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM do.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- Declaring a variable: type, name, value
- Static typing: the type is fixed and checked
- The eight primitive types
- Literals: how you write a value fixes its type
var: inferred, not dynamic- Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. Declaring a variable: type, name, value
You declare a variable by writing its type, then its name, then = and a starting value. int age = 25; reads as "make an int named age, starting at 25." The type (int, a whole number) comes first, and it isn't optional.
Output:
25Analysis. int age = 25; created a box of type int, named it age, and put 25 in it. System.out.println(age) looked up what age holds — 25 — and printed it. Notice that println(age) has no quotes, so it prints the variable's value, not the word "age". The box holds the number itself, not a pointer to it.
Intuition. Mechanism. A declaration does two things at once. It tells the compiler the variable's type, so the compiler can check every later use. And it reserves a box that holds the value directly. The type is decided right here, in the source, before the program runs.
Concrete bite. Leave out the type, and the compiler has no idea what age is. There's no box, and no permission to make one:
Compiler error:
Main.java:3: error: cannot find symbol
age = 25;
^
symbol: variable age
location: class Main
1 error"cannot find symbol" means the compiler looked for a declared variable named age, and didn't find one. A dynamically typed language, such as Python, decides types while the program runs, so there age = 25 would create the variable on the spot. Java insists that you declare it first, with a type.
💡 Earned rule. Declare before you assign, and declare with a type: int age = 25;. The cost is a few more keystrokes than in a language that creates variables on first use. The payoff: the compiler now knows age's type, and it catches every later line that misuses it, at compile time.
Declaring now, giving the value later
The starting value is optional. int age; makes the box and leaves it empty, and a later line assigns it a value with =. A variable declared inside a method, like every variable in this lesson, is called a local variable.
Output:
25Non-example: reading the box before anything is in it. The compiler tracks whether each local variable has definitely been given a value before any line reads it — Java calls this definite assignment [1]. Read an empty box, and the program doesn't compile:
Compiler error:
Main.java:4: error: variable x might not have been initialized
System.out.println(x);
^
1 error"Initialized" means "given its first value." Java doesn't quietly fill an empty local variable with 0. Instead, it refuses to compile, so a missing value gets caught before the program runs. The fix is to assign the variable on some line before the one that reads it.
Naming a variable
A variable's name is called an identifier, and the rules for it come from the language itself [2]:
- It is made of letters, digits,
_and$. - It must not start with a digit:
score2is fine,2ndis not. - It must not be a keyword, one of the words Java reserves for itself, such as
class,intorpublic[3]. - Case matters:
ageandAgeare two different names.
By convention, Java variable names use camelCase: start in lower case, and capitalize each later word, as in totalScore. Break the "no leading digit" rule, and javac's message doesn't even mention the name:
Compiler error:
Main.java:3: error: not a statement
int 2nd = 5;
^
Main.java:3: error: ';' expected
int 2nd = 5;
^
2 errorsA name can't start with a digit, so javac doesn't read 2nd as a name at all. It reads int on its own as a broken statement, and then trips over the rest. int class = 5; opens with the same two messages, not a statement and ';' expected. So when a declaration line gives you these errors, check the name first.
2. Static typing: the type is fixed and checked
Once a variable has a type, that type is fixed. You can change its value by reassigning it, but the new value must be of the variable's type, or convert to it without losing anything. The compiler checks this on every assignment.
Output:
25Analysis. score started at 10, and then we reassigned it to 25. Both are ints, so the compiler allowed it, and the box's content changed from 10 to 25. Reassignment changes the value, but never the type. Notice that the second line has no int: writing int score = 25; again would declare a second score, and javac rejects that with variable score is already defined.
Intuition. Mechanism. The type lives with the variable, not with whatever value you assign to it. On every assignment, the compiler checks the new value's type against the variable's declared type, and rejects any mismatch before the program runs.
Concrete bite. Try to put text into an int, and the compiler refuses. This is the classic Java type error:
Compiler error:
Main.java:3: error: incompatible types: String cannot be converted to int
int n = "hello";
^
1 error"hello" is text, and Java's type for text is String. (String isn't one of the eight primitives; Strings, the basics teaches it.) n is an int, and there's no automatic way to turn arbitrary text into an integer. So the compiler stops you right here, at compile time.
💡 Earned rule. A variable holds exactly its declared type, checked on every assignment. That way, a whole class of "wrong type of value" bugs gets caught before the program runs. The cost is rigidity: to turn text into a number, you have to convert it deliberately, using a method you'll meet in Input and output.
final: a value that never changes
Some values should never change once they're set: the number of days in a week, say, or a tax rate. Put final before the type, and the compiler allows exactly one assignment [4]. By convention, a constant's name is written in capitals, with _ between words.
Output:
21Non-example: reassigning a final variable. The type was never the only promise you could make. With final, the value is a promise too — and the compiler holds you to it:
Compiler error:
Main.java:4: error: cannot assign a value to final variable DAYS_IN_WEEK
DAYS_IN_WEEK = 8;
^
1 errorMark a variable final when its value must not change. That way, an accidental reassignment becomes a compile error, rather than a wrong answer.
3. The eight primitive types
Java has exactly eight primitive types. You'll use int, double, boolean, and char constantly; the other four are size variants you'll reach for only occasionally. Here are six of them at work:
Output:
42
8000000000
19.99
0.5
true
AAnalysis. Each line declared a box of a different type, and printed its value. The output shows three things:
longandintboth print as plain digits. TheLon8_000_000_000Ltells the compiler, "this value is along." It has to be, because the value is too big for anint.- The underscores in
8_000_000_000Lare just visual grouping; the compiler ignores them. char grade = 'A'uses single quotes for one character, and prints asA.
Each type's size is counted in bits. A bit is one binary digit, a 0 or a 1, and more bits can hold a wider range of values. Here's the whole set, grouped by family:
| Type | Holds | Example literal | Size | Range |
|---|---|---|---|---|
byte |
whole number | 120 |
8 bits | −128 to 127 |
short |
whole number | 1000 |
16 bits | −32768 to 32767 |
int |
whole number (default) | 42 |
32 bits | −2147483648 to 2147483647 |
long |
big whole number | 42L |
64 bits | −9223372036854775808 to 9223372036854775807 |
float |
decimal | 0.5f |
32 bits | fewer digits than double |
double |
decimal (default) | 19.99 |
64 bits | the default precision |
char |
one character | 'A' |
16 bits | character codes 0 to 65535 |
boolean |
true / false | true |
not specified | true or false |
The language fixes the whole-number ranges and the char range [5]. boolean has exactly two values, and its size in bits isn't defined [6]. You don't need to memorize the ranges, either: each whole-number type carries its own limits as MIN_VALUE and MAX_VALUE.
Output:
-128
127
-32768
32767
-2147483648
2147483647
-9223372036854775808
9223372036854775807Intuition.
Mechanism. Each primitive reserves a fixed number of bits, so it can represent only a fixed range of values. A byte has 8 bits, an int has 32, and a long has 64. The type isn't just a label; it decides how many bits the box has.
Concrete bite. Because the size is fixed, a value outside a type's range won't fit — and the compiler tells you so:
Compiler error:
Main.java:3: error: incompatible types: possible lossy conversion from int to byte
byte b = 200;
^
1 error200 is an ordinary int, but a byte tops out at 127. Storing 200 would lose information, so the compiler calls it a "lossy conversion" and refuses.
Why byte b = 100; compiles. 100 is an int literal too, yet this line compiles. That's because Java makes one exception for a fixed value written in the source, called a constant. A constant int may go into a byte, short or char variable when the value fits within that type's range [7]. The compiler checks the value itself, 100, and it fits.
Output:
100
30000
A
7.0The last line shows the other direction. An int goes into a double without needing any exception, because every int value fits. Numbers and arithmetic covers these conversions in full.
Non-example: the same value, in a variable. The exception applies to constants only. Put 100 into an ordinary int variable first, and the compiler no longer checks the value. It checks the type instead — and an int doesn't always fit in a byte:
Compiler error:
Main.java:4: error: incompatible types: possible lossy conversion from int to byte
byte b = n;
^
1 errorA final variable counts as a constant when its own value is one [4]. So final int n = 100; byte b = n; does compile, and prints 100.
💡 Earned rule. Choose the type based on the range you need:
intfor ordinary whole numbers, andlongwhen they can pass 2147483647;doublefor decimals;booleanandcharfor their obvious jobs;byte,shortorfloatonly when memory or an external format demands them.
The cost of a wrong choice is either a compile error (for a too-big value) or, worse, a silent overflow at run time. Overflow happens when arithmetic pushes a value past the type's range, without any warning. Numbers and arithmetic shows that trap.
4. Literals: how you write a value fixes its type
A literal is a value written directly in the source, like 25 or 'A'. The way you write it fixes its own type, independent of any variable — and that type must be compatible with wherever you put it.
- A plain whole number is an
int; a number with a decimal point is adouble[9]. - A suffix chooses otherwise:
Lforlong[8],fforfloat. - Single quotes make a
char; double quotes make aString.
Output:
25
25
3.14
3.14
A
AAnalysis. The printed values look identical in pairs, but the types differ:
25is anint, and25Lalong.3.14is adouble, and3.14fafloat.'A'is achar, and"A"aString.
The type is invisible in the output, but very real to the compiler. A lower-case l also makes a long, but it looks like the digit 1, which is why the language specification itself prefers L [8].
Intuition.
Mechanism. The compiler gives every literal a type based on how it's written, before considering where it goes. A bare integer literal is always an int, even when the variable next to it is a long.
Concrete bite. That's why a number too big for an int is an error, even when you "meant" a long:
Compiler error:
Main.java:3: error: integer number too large
int big = 3000000000;
^
1 error3000000000 is past an int's ceiling of 2147483647. The literal is an int by default, so the compiler rejects it before it ever reaches the variable. Mark it as a long literal by adding L, store it in a long box, and it fits:
Output:
3000000000Non-example: a decimal without its f. The same rule bites float. 0.5 is a double literal, and a double doesn't always fit in a float:
Compiler error:
Main.java:3: error: incompatible types: possible lossy conversion from double to float
float ratio = 0.5;
^
1 errorThe fix is the suffix: float ratio = 0.5f;, as in §3.
💡 Earned rule. Write the literal in the form of the type you want: L for long, f for float, single quotes for char, double quotes for String. A plain integer is an int, and a plain decimal is a double. The cost of forgetting is a compile error like "integer number too large". But it's an honest error: it catches, at compile time, a mismatch that would otherwise turn into a wrong number at run time.
5. var: inferred, not dynamic
Spelling a type out twice can get tedious. Since Java 10, var lets the compiler infer a local variable's type from its initializer — the value after = [10]. It's a convenience, not a new kind of typing. The variable still has one fixed type; you just didn't write it out.
Output:
42
19.99
AdaAnalysis. var count = 42 is exactly the same as int count = 42. The compiler reads the initializer 42, an int literal, and fixes count's type to int. Likewise, price becomes a double (from 19.99), and name becomes a String (from "Ada"). var copied the type from the value on the right; it didn't make the variable typeless.
Intuition.
Mechanism. var is resolved at compile time. The compiler looks at the initializer, works out its type, and writes that type into the variable as if you'd typed it yourself. The compiled bytecode is identical to the spelled-out version: no var is left at run time.
Concrete bite. So var isn't dynamic typing. The inferred type is just as fixed as a written one. Try to reassign across types, and it fails exactly as a spelled-out int would:
Compiler error:
Main.java:4: error: incompatible types: String cannot be converted to int
x = "hello"; // x is an int — it can't hold text
^
1 errorThe error names int, even though you wrote var. That's the proof: x's type was fixed to int the moment it was initialized.
💡 Earned rule. Use var when the initializer already makes the type obvious (var total = 0;, var name = "Ada";). Keep the explicit type wherever naming it helps the reader. Its boundaries are its cost [10]:
varneeds an initializer on the same line; otherwise, the compiler has nothing to infer from.- The initializer must have a type:
var n = null;fails withcannot infer type for local variable n. varworks only for local variables. A variable declared outside every method, or a method's parameter, gets'var' is not allowed here.
Non-example: var with no initializer. Leave out the initializer, and the compiler can't even guess:
Compiler error:
Main.java:3: error: cannot infer type for local variable y
var y;
^
(cannot use 'var' on variable without initializer)
1 error6. Mental-model summary
| Principle | Consequence |
|---|---|
| Java is statically typed: every variable's type is fixed at declaration | Declare with a type before use; the compiler checks every assignment |
| A local variable must be given a value before it is read | Reading an empty one is a compile error, never a silent 0 |
A name is letters, digits, _ and $, never starting with a digit, never a keyword |
2nd and class give not a statement, not a message about the name |
| A primitive variable holds its value directly | The box is the number; references hold a location instead |
The type is fixed; only the value can change, and final fixes the value too |
int n = "hello" and reassigning a final are compile errors |
| There are 8 primitives, each with a fixed size and range | byte b = 200 won't compile; int stops at 2147483647 |
A constant that fits may go into a byte, short or char |
byte b = 100 compiles; byte b = n does not, even when n is 100 |
| A literal's form fixes its own type | 25 is an int, 25L a long, 3.14 a double, 'A' a char, "A" a String |
var infers the type at compile time |
Convenience only: the type is still fixed; var x = 42; x = "hi" won't compile |
7. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
cannot find symbol … variable X |
X was never declared, or its spelling or case differs from the declaration (Age vs age) |
declare it with a type, or match the declared name exactly |
variable x might not have been initialized |
the variable is read before any line gives it a value | assign it before the line that reads it, or give it a starting value in the declaration |
not a statement, then ';' expected, on a declaration |
the name starts with a digit (2nd) or is a keyword (class) |
rename it: second, className |
variable x is already defined in method main(String[]) |
x was declared twice, often by writing its type again on a reassignment |
drop the type on the second line: x = 2; |
incompatible types: String cannot be converted to int |
text went into a number box (or a reassignment crossed types) | fix the value, or convert it deliberately |
possible lossy conversion from int to byte |
the value is out of range, or it is a variable rather than a constant | use a wider type (int, long), or a cast (Numbers and arithmetic) |
possible lossy conversion from double to float |
a decimal literal without f went into a float |
add f (0.5f), or use double |
integer number too large |
a whole-number literal is past 2147483647 | add L, and store it in a long |
cannot assign a value to final variable X |
a final variable was reassigned |
remove the reassignment, or drop final if the value must change |
cannot infer type for local variable |
var with no initializer, or with null |
give it a typed starting value, or write the type |
'var' is not allowed here |
var on a variable outside a method, or on a parameter |
write the type |
✅ Check yourself
One check per objective. Answer before you open anything.
After var d = 5;, why does d = 5.5; fail? And why can't a method's parameter be declared with var?
var d = 5; gives d the type int, from the int literal 5. 5.5 is a double, so javac says incompatible types: possible lossy conversion from double to int. var fixed the type just as firmly as writing int would have.
var needs an initializer to copy a type from, and a parameter doesn't have one. It works only for local variables; on a parameter, javac says 'var' is not allowed here [10].
The 🧪 box below: which two of the four declarations compile?
int a = 5; and byte c = 5; compile. 5 is an int literal, and since it's a constant, it fits in a byte.
int b = 5.0; fails: 5.0 is a double literal, so javac says possible lossy conversion from double to int. var d = 5; d = 5.5; fails with the same message, because d is an int.
📚 Sources
- The Java Language Specification, Java SE 25, chapter 16 "Definite Assignment" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-16.html
- The Java Language Specification, Java SE 25, §3.8 "Identifiers" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.8
- The Java Language Specification, Java SE 25, §3.9 "Keywords" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.9
- The Java Language Specification, Java SE 25, §4.12.4 "
finalVariables" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.12.4 - The Java Language Specification, Java SE 25, §4.2.1 "Integral Types and Values" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.2.1
- The Java Tutorials, "Primitive Data Types" (
boolean: "its 'size' isn't something that's precisely defined") — https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html - The Java Language Specification, Java SE 25, §5.2 "Assignment Contexts" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html#jls-5.2
- The Java Language Specification, Java SE 25, §3.10.1 "Integer Literals" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.10.1
- The Java Language Specification, Java SE 25, §3.10.2 "Floating-Point Literals" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.10.2
- JEP 286: Local-Variable Type Inference (JDK 10) — https://openjdk.org/jeps/286
🧪 Predict, then check. Write these four declarations, and predict for each one whether it compiles — and if not, what the error says: int a = 5; · int b = 5.0; · byte c = 5; · var d = 5; d = 5.5;. Two compile and two don't. Decide which, and why, before you run them.
Hint: think about each literal's own type, 5 versus 5.0, and which box it's going into.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.