Module 2 · Core Syntax & Types ⏱ 19 min

Variables & Types

By the end of this lesson you will be able to:
  • Declare Java's core primitive types — int, double, boolean, and char — and a String
  • Explain what final does and why a value can't be reassigned after it's set
  • Describe how static typing catches mistakes at build time rather than at runtime

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, like 13 or -200
  • double — a number with a decimal point, like 5.99
  • boolean — only true or false
  • char — 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
Primitives store the value directly on the stack; references store an address pointing to an object on the heap.

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 camelCaseitemCount, not item_count. Constants declared with final traditionally use SCREAMING_SNAKE_CASEMAX_SIZE — so they stand out as values that must never change.

Main.java — one variable of each common type. Real, compiling Java.
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.

Main.java — integer division silently drops the remainder; mixing with double promotes the result. Real, compiling Java.
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.

Main.java — `final` prevents accidental reassignment of constants. Real, compiling Java.
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
`'A'` is a primitive char stored directly; `"A"` is a String object accessed by reference. They are not interchangeable.
Exercise

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);
    }
}
Exercise

Which line declares a value that the compiler will refuse to let you change afterwards?

Exercise

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));
    }
}
Exercise

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);
    }
}
Exercise

What is the value of int result = 9 / 4;?

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.
  • final means assign once, never again. Use it for constants and configuration values.
  • Integer division silently discards the remainder: 5 / 2 is 2. Use double operands when you need fractions.
  • Mixing int and double promotes the int to double; storing a double into an int requires 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.

Checkpoint quiz

Which of these will the Java compiler reject as a type mistake?

What is the difference between char and String in Java?

Go deeper — technical resources