Every value in Java has a type, and you declare it up front. That is the deal Java makes with you: tell the compiler what kind of thing a variable holds, and in return it will refuse to let you put the wrong kind of thing in it. It is more typing than Python — but the mistakes it prevents are real.
Consider a method that expects a price. In a dynamically typed language, a caller might pass the string "5.99" instead of the number 5.99. The program runs, reaches the calculation, and crashes with a type error at runtime — possibly in production. In Java, the compiler sees double price = "5.99"; and rejects it immediately. The bug never ships. That is the static typing bargain: verbosity now for safety later.
Primitives vs references
Java has a small set of primitives — the simple values stored directly — and references, which point at objects. The four primitives you will meet first:
int— a whole number, like13or-200double— a number with a decimal point, like5.99boolean— onlytrueorfalsechar— a single character in single quotes, like'A'
The most common reference type is String — text in double quotes, like "Ada". A String is an object, not a primitive, but you can treat it like plain text.
Primitives live on the stack: fast to create and destroy, fixed size, and the variable holds the actual value. References live on the stack too, but they merely store the memory address of an object that lives on the heap. That distinction matters when you learn about object assignment and equality later: int a = b; copies the value, while String s = t; copies the reference, not the text.
flowchart TD
subgraph Stack
A["int count = 7"]
B["String name"] --> HEAP
end
subgraph Heap
HEAP[("String object<br/>Java")]
end
style Stack fill:#e2e8f0
style Heap fill:#fed7aa
Declaring and using variables
The pattern is always type, name, equals, value, semicolon. Notice that char uses single quotes and String uses double quotes — getting those mixed up is a compile error, not a runtime surprise.
Java also enforces definite assignment: the compiler will not let you read a local variable before you have given it a value. int x; System.out.println(x); fails immediately. This sounds strict, but it eliminates an entire class of bugs where an uninitialised variable accidentally holds zero or garbage.
Names should start with a letter and use camelCase — itemCount, not item_count. Constants declared with final traditionally use SCREAMING_SNAKE_CASE — MAX_SIZE — so they stand out as values that must never change.
public class Main {
public static void main(String[] args) {
int count = 7;
double price = 4.5;
boolean active = true;
char letter = 'J';
String name = "Java";
final int MAX = 3;
// MAX = 5; // would NOT compile: a 'final' variable cannot be reassigned
System.out.println(name + ": count=" + count);
}
}
Mixing types: promotion and casting
When an int meets a double, Java promotes the int to double before the operation. 3 + 2.5 becomes 3.0 + 2.5, and the result is 5.5. This is convenient, but it only goes one way: if you store that result back into an int, the compiler complains about possible data loss. You must explicitly cast with (int), which tells the compiler you accept the truncation.
There is a darker trap: dividing two ints produces an int, discarding the remainder. 5 / 2 is 2, not 2.5. If you expected a decimal, the bug is silent — the compiler does not warn you because both operands are integers and integer division is perfectly legal. The fix is to make one operand a double: 5.0 / 2 gives 2.5.
public class Main {
public static void main(String[] args) {
int a = 5;
int b = 2;
System.out.println("int division: " + (a / b));
System.out.println("mixed division: " + (a / 2.0));
double result = a + 2.5;
System.out.println("promotion: " + result);
}
}
final: the const you can trust
The final keyword means a variable can be assigned exactly once. After final int MAX = 3;, any later MAX = 5; is a compile error. This is not a suggestion; the compiler enforces it absolutely.
Use final for any value that should not drift: configuration limits, magic numbers, and loop bounds. It communicates intent to the next reader — this number is fixed by design, not oversight. It also catches typos: if you meant to update currentScore but accidentally typed MAX_SCORE, the compiler stops you. In larger codebases, many teams enforce final by default on local variables because it eliminates an entire category of reassignment bugs.
public class Main {
public static void main(String[] args) {
final double PI = 3.14159;
int radius = 5;
double area = PI * radius * radius;
System.out.println("Area: " + area);
// PI = 3.14; // compile error
}
}
Why static typing helps
When you join a String and a number with +, Java quietly converts the number to text — so "Price each: " + 5.99 becomes "Price each: 5.99". That is convenient. But if you tried to assign that same text back into an int, the compiler would stop you before the program ever ran. The error happens at build time, not at 3 a.m. That early failure is the whole point of static typing.
Static typing also powers the IDE features you met in the previous lesson. Because every variable's type is known, autocomplete can list only valid methods, and refactoring can rename a method across the entire codebase without missing a single call site. The type system is not just a gatekeeper; it is the foundation that makes every other tool possible.
flowchart LR C["'A'"] --> CT["char primitive<br/>2 bytes on stack"] S["\"A\""] --> ST["String object<br/>on heap (reference)"] style CT fill:#c2410c,color:#fff style ST fill:#3776ab,color:#fff
What does this program print? Read it carefully and type the exact output (five lines).
public class Main {
public static void main(String[] args) {
int apples = 10;
int oranges = 3;
int total = apples + oranges;
double price = 5.99;
boolean inStock = total > 0;
char grade = 'A';
final String STORE = "Corner Market";
System.out.println(STORE);
System.out.println("Fruit count: " + total);
System.out.println("Price each: " + price);
System.out.println("In stock: " + inStock);
System.out.println("Grade: " + grade);
}
}
apples + orangesis10 + 3 = 13.total > 0istrue, and abooleanprints as the wordtrue.Joining
"Price each: " + priceturns the double into its text form,5.99.
Which line declares a value that the compiler will refuse to let you change afterwards?
final marks the variable as assignable exactly once. Any later reassignment is a compile error — which is exactly what you want for a value that must never change.
What does this program print? Read it carefully and type the exact output (two lines).
public class Main {
public static void main(String[] args) {
int items = 7;
double price = 3.5;
int total = (int)(items * price);
System.out.println("Total: " + total);
System.out.println("Exact: " + (items * price));
}
}
items * priceis7 * 3.5 = 24.5.Casting
(int)truncates the decimal, sototalbecomes24.The second line prints the uncut
doublevalue.
What does this program print? Read it carefully and type the exact output (two lines).
public class Main {
public static void main(String[] args) {
final int BASE = 10;
int bonus = 3;
int score = BASE + bonus;
System.out.println(score);
bonus = 5;
score = BASE + bonus;
System.out.println(score);
}
}
BASEis final but can be read many times; it never changes.bonusis reassigned from3to5, soscoreis recalculated.
What is the value of int result = 9 / 4;?
Both operands are int, so Java performs integer division and silently discards the remainder. 9 / 4 is 2. To get 2.25, at least one operand must be a double.
Recap
- Every variable has a type declared up front. The compiler rejects mismatches before the program runs.
- Primitives (
int,double,boolean,char) store values directly on the stack; references (String, objects) point to heap memory. finalmeans assign once, never again. Use it for constants and configuration values.- Integer division silently discards the remainder:
5 / 2is2. Usedoubleoperands when you need fractions. - Mixing
intanddoublepromotes theinttodouble; storing adoubleinto anintrequires an explicit cast and truncates. - Java enforces definite assignment: you cannot read a local variable before initialising it.
Next you will meet Java's control flow — if, else, and loops — and discover how static typing shapes the conditions you write.