Variables and data types
Java is statically typed: every variable has a type that the compiler checks before the program runs.
There are 8 primitive types stored directly as values: byte, short, int, long, float, double, char and boolean. Everything else is a reference type that points to an object on the heap, such as String, arrays and your own classes.
Each primitive has a wrapper class (Integer, Double, Boolean…) so it can be stored in collections. Java converts between them automatically; this is called autoboxing (Java 5).
Since Java 10 you can write var for local variables and let the compiler infer the type. The type is still fixed at compile time; var is not dynamic typing.
The eight primitive types
byte(8-bit, -128 to 127),short(16-bit),int(32-bit, about ±2.1 billion),long(64-bit)float(32-bit) anddouble(64-bit) for decimalschar(a 16-bit UTF-16 unit, such as 'A')boolean(trueorfalse)
Use int for whole numbers, long for large counts, ids and timestamps, double for measurements, and BigDecimal for money.
Literals: writing values in code
Underscores make big numbers readable. Prefixes choose the base, and suffixes choose the type.
int million = 1_000_000;
int mask = 0b1010_1010; // binary
int colour = 0xFF8800; // hexadecimal
long big = 9_000_000_000L; // L for long
float ratio = 0.75f; // f for float
char letter = 'A', rupee = '\u20B9';
int oops = 017; // octal! this is 15, not 17var: local type inference
Since Java 10, var lets the compiler infer a local variable's type from its initializer. It's still statically typed. It works only for local variables that are initialised, not for fields, parameters or return types. Use it when the type is obvious from the right-hand side.
var names = new ArrayList<String>(); // ArrayList<String>
var total = 0L; // long
for (var entry : map.entrySet()) { } // Map.Entry<K, V>
// var x; // compile error: needs an initializer
// var n = null; // compile error: type can't be inferredType conversion and casting
Widening (smaller to larger, such as int to long or double) happens automatically. Narrowing needs an explicit cast and can lose information: decimals are truncated and large values wrap around.
int i = 42;
long l = i; // widening: automatic
double d = i; // 42.0
int truncated = (int) 3.99; // 3
byte wrapped = (byte) 200; // -56 (200 doesn't fit in a byte)
char c = (char) 66; // 'B'
int code = 'A'; // 65Wrapper classes and autoboxing
Each primitive has a wrapper class: Integer, Long, Double, Character, Boolean and so on. Collections need objects, so Java converts automatically (autoboxing and unboxing). Two traps: unboxing null throws NullPointerException, and == on wrappers compares references.
List<Integer> scores = new ArrayList<>();
scores.add(90); // autoboxing int -> Integer
int first = scores.get(0); // unboxing
Integer a = 127, b = 127, x = 128, y = 128;
System.out.println(a == b); // true (cached values -128..127)
System.out.println(x == y); // false (different objects)
System.out.println(x.equals(y)); // true: always use equals
Integer missing = null;
int boom = missing; // NullPointerExceptionDefault values, scope and constants
Fields get default values (0, false, null); local variables don't and must be assigned before use. A variable lives only inside the block ({ }) where it's declared. Mark values that never change as final; constants are static final and written in UPPER_SNAKE_CASE.
Integer overflow
Integer arithmetic silently wraps around when it goes past the maximum. Use long, the Math.*Exact methods that throw on overflow, or BigInteger for arbitrarily large numbers.
int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648
long ok = (long) max + 1; // 2147483648
Math.addExact(max, 1); // throws ArithmeticException
BigInteger huge = BigInteger.TWO.pow(100);Example
int age = 30; // 32-bit whole number
long population = 8_100_000_000L; // underscores allowed since Java 7
double price = 499.99;
char grade = 'A';
boolean active = true;
String name = "Asha";
Integer boxed = age; // autoboxing
int back = boxed; // unboxing
var cities = new ArrayList<String>(); // Java 10+: inferred as ArrayList<String>
cities.add("Pune");Common mistake
Comparing wrapper objects with ==. Integer a = 1000, b = 1000; a == b is false. Use a.equals(b).
Under the hood
Local primitives live in the method's stack frame and cost no allocation, while wrappers are heap objects. Integer caches the values -128 to 127, which is why == seems to "work" for small numbers and then fails for larger ones. Integer overflow wraps silently: Integer.MAX_VALUE + 1 is negative. Use Math.addExact or a long when that matters, and BigDecimal for money.
Check yourself
What does Integer a = 128, b = 128; a == b give?
How this connects
Know these first
Where this leads
Part of Java from zero, Upgrade from Java 8 to Java 25.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.