Primitive Types and Variables

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Java has eight built-in primitive types that hold plain values — not objects — each with a fixed size and a defined range. This page follows dev.java "Primitive Types", dev.java "Using the var Keyword", and JLS chapter 5, "Conversions and Contexts".

The Eight Primitive Types

type      bits   range / values                                default
---------------------------------------------------------------------------
byte       8     -128 .. 127                                    0
short      16    -32,768 .. 32,767                              0
int        32    -2,147,483,648 .. 2,147,483,647               0
long       64    -9,223,372,036,854,775,808 .. 9.22e18          0L
float      32    IEEE 754, ~6-7 significant decimal digits      0.0f
double     64    IEEE 754, ~15-16 significant decimal digits    0.0
char       16    U+0000 .. U+FFFF (one unsigned UTF-16 code unit)   U+0000
boolean    --    true / false                                   false
int count = 0;
long fileSize = 9_000_000_000L;          // exceeds int range: needs the L suffix
double ratio = 3.0 / 7.0;
char grade = 'A';
boolean done = false;

class Config {
    int retries;        // a FIELD without an initializer defaults to 0
    boolean verbose;    // defaults to false
    double timeout;     // defaults to 0.0
}

Primitives have no methods and can never be null; each has a companion wrapper class (Integer, Long, Double, Character, Boolean, …​) covered on Numbers and Math. Fields and array elements are automatically zero-initialized (0, 0.0, false, \u0000); local variables are not (see definite assignment below). int is the default type for integer literals and integer arithmetic. The exact list is JLS 4.2.

Literals

int dec = 1_000_000;         // underscores group digits; not at the ends, not next to the dot
int hex = 0xFF;              // 255
int bin = 0b1010_0101;       // 165
int oct = 0777;              // leading zero means octal -> 511  (avoid: easy to misread)

long big  = 123_456_789L;    // L suffix (never lowercase l -- it looks like 1)
float f   = 3.14f;           // f suffix required: a decimal literal is double by default
double d  = 6.022e23;        // exponent notation
double d2 = 42d;             // optional d suffix

char tab   = '\t';           // \t \n \r \f \b \\ \' \"
char quote = '\'';
char eacute = '\u00e9';     // the letter e-acute, written as a Unicode escape
int code   = 'A';            // 65  -- a char promotes to int in numeric context

boolean ok = true;           // only true / false
String s   = null;           // null is assignable to any reference type

Underscores may appear only between digits. A plain decimal point makes a double; add f for a float. See the Java Tutorials "Primitive Data Types" page and JLS 3.10. String literals and text blocks are on Strings and Text.

Declaring and Initializing Variables

int a = 5;                  // declaration with an initializer
int b, c;                   // two variables, still uninitialized
b = 1;
c = b + a;

final int LIMIT = 100;      // a final local: assigned exactly once
// LIMIT = 200;             // ERROR: cannot assign a value to final variable LIMIT

final int mode;             // a "blank final": assign later, once, on every path
if (a > 0) {
    mode = 1;
} else {
    mode = -1;
}

int unset;
// System.out.println(unset);   // ERROR: variable unset might not have been initialized

The compiler enforces definite assignment (JLS chapter 16): a local variable must be provably assigned on every path that reaches a read of it. final on a local or a parameter means "assigned once" — required for locals captured by a lambda or anonymous class, and the right default for values that should not change. Declaring one variable per line reads better than the comma form.

var: Local-Variable Type Inference

var names = new ArrayList<String>();     // inferred as ArrayList<String>
var count = 0;                           // int
var total = 0L;                          // long
var path  = Path.of("in.txt");           // Path

for (var entry : System.getenv().entrySet()) {   // Map.Entry<String, String>
    System.out.println(entry.getKey());
}

try (var in = Files.newBufferedReader(path)) {   // BufferedReader
    in.readLine();
}

// NOT allowed:
// var x;                // no initializer to infer from
// var y = null;         // null has no type
// var z = () -> 42;     // a lambda needs an explicit target type
// var w = { 1, 2, 3 };  // an array initializer needs a declared type

var (Java 10 and later) infers the static type of a local variable from its initializer. It is not allowed for fields, method parameters, method return types, or catch parameters, and it always needs an initializer. The variable is still statically typed — var is neither Object nor dynamic typing. Use it when the type is obvious from the right-hand side (a constructor or a well-named factory method) and spell the type out when doing so aids the reader. Full guidance is at dev.java "Using the var Keyword".

Conversions, Casts, and Overflow

// Widening: automatic and lossless (int/long -> float/double may lose precision, but not range)
int i = 1_000;
long l = i;                    // int  -> long
double dd = l;                 // long -> double

// Narrowing: requires an explicit cast and may discard information
double pi = 3.99;
int truncated = (int) pi;      // 3   -- the fraction is dropped, not rounded
long huge = 4_000_000_000L;
int wrapped = (int) huge;      // -294967296  -- high bits discarded
byte tiny = (byte) 200;        // -56

char ch = (char) 97;           // 'a'
int back = 'a';                // 97

// Integer overflow is SILENT: it wraps modulo 2^n
int max = Integer.MAX_VALUE;
int oops = max + 1;            // -2147483648

// Ask for an exception instead
int safe = Math.addExact(max, 1);              // throws ArithmeticException: integer overflow
long prod = Math.multiplyExact(1_000_000L, 1_000_000L);
int narrowed = Math.toIntExact(10_000_000_000L);   // throws: value out of int range

Widening primitive conversions (byteshortintlongfloatdouble, and charint) happen automatically (JLS 5.1.2). Narrowing conversions (JLS 5.1.3) require an explicit (type) cast and can silently lose range or precision.

Integer arithmetic never traps on overflow — it wraps. When a wrong answer would be dangerous, use the exact methods on Math (addExact, subtractExact, multiplyExact, negateExact, incrementExact, toIntExact), which throw ArithmeticException on overflow. Floating-point follows IEEE 754: it has signed zeros, Infinity, and NaN, and never throws on division by zero (1.0 / 0 is Infinity, 0.0 / 0.0 is NaN). Integer division or remainder by zero does throw ArithmeticException.

See Also