Skip to content
C Codeloom
Java

Java Variables and Primitive Types

Master Java primitives, reference types, literals, and casting. Know exactly what each type costs and where overflow and precision will bite you.

·5 min read · By Yash Kesharwani
Beginner 8 min read

What you'll learn

  • The eight primitive types and their exact ranges
  • When to use a wrapper class instead of a primitive
  • Literal syntax for ints, longs, doubles, and chars
  • Implicit widening and when an explicit cast is required
  • How var works and where it hurts readability

Java is statically typed: every variable has a type known at compile time, and the compiler refuses to let you mix incompatible types without an explicit cast. That strictness is a feature. It catches bugs before the program runs.

The eight primitives

Primitives are value types. They live on the stack (or inside an object), not on the heap, and they cost exactly what their bit width says.

TypeBitsRange or precision
byte8-128 to 127
short16-32,768 to 32,767
int32about plus or minus 2.1 billion
long64about plus or minus 9.2 quintillion
float32~7 decimal digits
double64~15 decimal digits
char16one UTF-16 code unit
boolean1true or false
int answer = 42;
long bigNumber = 9_000_000_000L;   // L suffix required
double price = 19.99;
float ratio = 0.5f;                // f suffix required
char letter = 'J';
boolean ready = true;

The underscore in 9_000_000_000L is a digit separator the compiler ignores. Use it for readability.

Reference types

Anything not in the table above is a reference type: classes, arrays, interfaces, enums. A reference variable holds a pointer to an object on the heap, or null.

String name = "Codeloom";   // reference
int[] scores = {90, 85, 72}; // reference to an array
String missing = null;       // legal for references, not primitives

Trying to assign null to an int is a compile error. That alone prevents a class of bugs you would hit in JavaScript or Python.

Wrapper classes

Each primitive has a boxed class: Integer, Long, Double, Boolean, Character, and so on. Use them when a generic container demands an object (you cannot have a list of int, only a list of Integer).

import java.util.List;

List<Integer> nums = List.of(1, 2, 3);   // autoboxed from int
int first = nums.get(0);                 // auto-unboxed back to int

Watch for two traps:

  1. Wrappers can be null. Unboxing null throws NullPointerException.
  2. Comparing wrappers with == compares references, not values. Use .equals() or unbox first.

Literals

Numeric literals default to int and double. Add a suffix to override.

int hex = 0xFF;          // 255
int binary = 0b1010;     // 10
long big = 1_000_000L;
double d = 1.5e3;        // 1500.0
char nl = '\n';
char unicode = 'Ω'; // omega
String s = "Hello";

Text blocks (Java 15+) let you write multi line strings without escaping:

String json = """
    {
      "name": "Codeloom",
      "year": 2026
    }
    """;

Widening and narrowing

Java widens automatically when there is no loss of information:

int i = 100;
long l = i;       // implicit widening
double d = l;     // implicit widening

Narrowing requires an explicit cast and may lose data:

double pi = 3.14159;
int trimmed = (int) pi;   // 3, fractional part dropped

int big = 130;
byte b = (byte) big;      // overflows to -126

The cast is the compiler asking you to confirm you know what you are doing.

Overflow is silent

Integer arithmetic wraps around on overflow without throwing.

int max = Integer.MAX_VALUE;
System.out.println(max + 1);   // -2147483648

If you need overflow checks, use Math.addExact, Math.multiplyExact, etc. They throw ArithmeticException instead of wrapping.

Floating point is not exact

double and float follow IEEE 754. They cannot represent 0.1 exactly.

System.out.println(0.1 + 0.2);  // 0.30000000000000004

For money, use BigDecimal:

import java.math.BigDecimal;

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));   // 0.3

Always construct BigDecimal from a String, never from a double.

var: local type inference

Since Java 10 you can let the compiler infer a local variable type with var.

var name = "Codeloom";        // inferred as String
var nums = List.of(1, 2, 3);  // inferred as List<Integer>

var only works for local variables with an initializer. It is not dynamic typing; the type is still fixed at compile time. Use it when the right hand side already names the type (constructors, factory methods). Avoid it when it hides the type from a reader, like var x = process(data);.

Constants

Use final for values that should never be reassigned:

final double PI = 3.14159;
final int MAX_RETRIES = 3;

Combine with static at class level for true constants. By convention, constants are UPPER_SNAKE_CASE.

Wrap up

Pick primitives by default, reach for wrappers when generics force you, and use BigDecimal for money. Next, learn how to drive these values with branches and loops in Java Control Flow, or jump ahead to Java Classes and Objects when you are ready to model real things.