00:00

Data Types in Java – Primitive and Reference Types

A data type tells Java what kind of value a variable can hold and what you can do with it: whole numbers, decimals, characters, true/false values, or references to objects. Because Java is statically typed, every variable has a type that is fixed when you compile, and the compiler checks that you use it correctly.

Java has two families of types: the eight primitive types, which hold simple values directly, and reference types, which point to objects. This tutorial covers both, along with literals, conversions and common pitfalls.

1. Two Kinds of Data Types

Classification of data types in Java
Primitive typesReference types
8 built-in types: byte, short, int, long, float, double, char, booleanClasses, interfaces, arrays, enums, records
Hold the value itselfHold a reference to an object on the heap
Names start with a lowercase letterNames usually start with an uppercase letter (String)
Cannot be nullCan be null
No methodsHave methods ("hi".length())
Fixed sizeSize depends on the object

2. The Eight Primitive Types

TypeSizeHoldsDefaultExample
byte8 bitsWhole numbers −128 to 1270byte age = 25;
short16 bitsWhole numbers −32,768 to 32,7670short year = 2026;
int32 bitsWhole numbers about ±2.1 billion0int population = 1_400_000;
long64 bitsWhole numbers about ±9.2 × 10180Llong views = 9_000_000_000L;
float32 bitsDecimals, about 7 significant digits0.0ffloat rating = 4.5f;
double64 bitsDecimals, about 15–16 significant digits0.0double pi = 3.14159;
char16 bitsOne UTF-16 character, 0 to 65,535'\u0000'char grade = 'A';
booleanJVM-dependenttrue or falsefalseboolean active = true;

The exact limits come from constants in the wrapper classes:

PrimitiveRanges.java
public class PrimitiveRanges {
    public static void main(String[] args) {
        System.out.printf("%-8s %-7s %-22s %s%n", "type", "bits", "min", "max");
        System.out.printf("%-8s %-7d %-22d %d%n", "byte", Byte.SIZE, Byte.MIN_VALUE, Byte.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22d %d%n", "short", Short.SIZE, Short.MIN_VALUE, Short.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22d %d%n", "int", Integer.SIZE, Integer.MIN_VALUE, Integer.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22d %d%n", "long", Long.SIZE, Long.MIN_VALUE, Long.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22s %s%n", "float", Float.SIZE, Float.MIN_VALUE, Float.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22s %s%n", "double", Double.SIZE, Double.MIN_VALUE, Double.MAX_VALUE);
        System.out.printf("%-8s %-7d %-22d %d%n", "char", Character.SIZE, (int) Character.MIN_VALUE, (int) Character.MAX_VALUE);
        System.out.printf("%-8s %-7s %-22s %s%n", "boolean", "-", false, true);
    }
}
Output
type     bits    min                    max
byte     8       -128                   127
short    16      -32768                 32767
int      32      -2147483648            2147483647
long     64      -9223372036854775808   9223372036854775807
float    32      1.4E-45                3.4028235E38
double   64      4.9E-324               1.7976931348623157E308
char     16      0                      65535
boolean  -       false                  true

Note that Float.MIN_VALUE and Double.MIN_VALUE are the smallest positive values, not the most negative ones. The most negative double is -Double.MAX_VALUE.

3. Choosing the Right Primitive Type

  • int is the default for whole numbers (counters, indexes, quantities).
  • long for values that can exceed about 2 billion: timestamps in milliseconds, file sizes, IDs.
  • double is the default for decimals (measurements, scientific values).
  • byte and short mainly for large arrays or binary data where memory matters.
  • float mainly for graphics or large arrays where precision is less important.
  • Never use float or double for money; use BigDecimal or an integer number of cents.

4. Default Values

Fields of a class get default values automatically. Local variables do not: they must be assigned before use, or the program does not compile.

DefaultValues.java
public class DefaultValues {
    // Fields get default values automatically
    static byte b; static short s; static int i; static long l;
    static float f; static double d; static char c; static boolean flag;
    static String text;

    public static void main(String[] args) {
        System.out.println("byte=" + b + " short=" + s + " int=" + i + " long=" + l);
        System.out.println("float=" + f + " double=" + d);
        System.out.println("char=" + (int) c + " (the \\u0000 character)  boolean=" + flag);
        System.out.println("String (reference)=" + text);

        int local;
        // System.out.println(local);   // compile error: variable local might not have been initialized
    }
}
Output
byte=0 short=0 int=0 long=0
float=0.0 double=0.0
char=0 (the \u0000 character)  boolean=false
String (reference)=null

5. Literals

A literal is a value written directly in the code. Java supports several notations:

Literals.java
public class Literals {
    public static void main(String[] args) {
        int million = 1_000_000;          // underscores for readability
        int hex = 0xFF;                   // hexadecimal
        int binary = 0b1010;              // binary
        int octal = 017;                  // octal (leading 0)
        long big = 9_000_000_000L;        // L suffix: long
        float price = 19.99f;             // f suffix: float
        double pi = 3.14159;              // decimal literals are double by default
        double sci = 6.02e23;             // scientific notation
        char letter = 'A';
        char rupee = '₹';            // Unicode escape
        boolean done = true;

        System.out.println(million + " " + hex + " " + binary + " " + octal);
        System.out.println(big + " " + price + " " + pi + " " + sci);
        System.out.println(letter + " " + rupee + " " + done);
    }
}
Output
1000000 255 10 15
9000000000 19.99 3.14159 6.02E23
A ₹ true
  • Whole-number literals are int by default; add L for long.
  • Decimal literals are double by default; add f for float.
  • Underscores (1_000_000) are ignored by the compiler and make large numbers readable.
  • Character literals use single quotes; string literals use double quotes.

6. Reference Types

Everything that is not a primitive is a reference type:

  • Classes: String, LocalDate, your own classes.
  • Interfaces: List, Runnable.
  • Arrays: int[], String[][]; even arrays of primitives are objects.
  • Enums and records.

A reference variable stores the location of an object, not the object itself. Its default value is null. String is the most used reference type; although it has literal syntax ("hello"), it is a class, not a primitive.

7. Type Conversion and Casting

Widening conversions (to a larger type) happen automatically. Narrowing conversions (to a smaller type) need an explicit cast and can lose information:

TypeConversion.java
public class TypeConversion {
    public static void main(String[] args) {
        // Widening (automatic): small type -> bigger type
        int count = 42;
        long bigCount = count;
        double asDouble = count;
        System.out.println("widening:  " + bigCount + ", " + asDouble);

        // Narrowing (explicit cast): bigger type -> smaller type, may lose data
        double price = 99.99;
        int whole = (int) price;              // fraction is dropped, not rounded
        System.out.println("narrowing: " + whole + ", rounded: " + Math.round(price));

        int large = 300;
        byte small = (byte) large;            // 300 does not fit in a byte: wraps around
        System.out.println("(byte) 300 = " + small);

        // Integer overflow wraps silently
        int max = Integer.MAX_VALUE;
        System.out.println("MAX + 1 = " + (max + 1));
        System.out.println("addExact: " + safeAdd(max, 1));

        // Integer division
        System.out.println("7 / 2 = " + (7 / 2) + ", 7 / 2.0 = " + (7 / 2.0));

        // char is a number too
        char c = 'A';
        System.out.println("'A' + 1 = " + (c + 1) + ", (char) ('A' + 1) = " + (char) (c + 1));
    }

    static String safeAdd(int a, int b) {
        try { return String.valueOf(Math.addExact(a, b)); }
        catch (ArithmeticException e) { return "ArithmeticException: " + e.getMessage(); }
    }
}
Output
widening:  42, 42.0
narrowing: 99, rounded: 100
(byte) 300 = 44
MAX + 1 = -2147483648
addExact: ArithmeticException: integer overflow
7 / 2 = 3, 7 / 2.0 = 3.5
'A' + 1 = 66, (char) ('A' + 1) = B
  • Widening order: byte → short → int → long → float → double, and char → int.
  • Casting a decimal to an integer truncates the fraction; use Math.round() to round.
  • Integer overflow wraps around silently. Use Math.addExact() and friends to detect it, or long/BigInteger for bigger values.
  • Dividing two integers gives an integer; make one operand a double to get a decimal result.
  • In expressions, byte, short and char are promoted to int.

8. Wrapper Classes and Autoboxing

Each primitive has a wrapper class: Byte, Short, Integer, Long, Float, Double, Character and Boolean. Collections can only store objects, so Java converts automatically between primitives and wrappers (autoboxing and unboxing).

WrapperTypes.java
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class WrapperTypes {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);                       // autoboxing: int -> Integer
        int first = numbers.get(0);           // unboxing: Integer -> int
        System.out.println("first = " + first);

        int parsed = Integer.parseInt("123"); // String -> int
        String back = String.valueOf(456);    // int -> String
        System.out.println(parsed + 1 + " and " + back + "1");

        Integer a = 127, b = 127, x = 1000, y = 1000;
        System.out.println("127 == 127: " + (a == b) + ", 1000 == 1000: " + (x == y) + ", equals: " + x.equals(y));

        // Floating-point is approximate; use BigDecimal for money
        System.out.println("0.1 + 0.2 = " + (0.1 + 0.2));
        System.out.println("BigDecimal: " + new BigDecimal("0.1").add(new BigDecimal("0.2")));
    }
}
Output
first = 5
124 and 4561
127 == 127: true, 1000 == 1000: false, equals: true
0.1 + 0.2 = 0.30000000000000004
BigDecimal: 0.3
  • Compare wrapper objects with equals(), not ==. Small values (−128 to 127) are cached, so == works for them by accident but fails for larger values.
  • Unboxing a null wrapper throws NullPointerException.
  • 0.1 + 0.2 is not exactly 0.3 in binary floating point; BigDecimal gives exact decimal results.

More in Wrapper Classes in Java.

9. Local Variable Type Inference with var

Since Java 10, you can write var for local variables and let the compiler infer the type from the initial value. The variable is still statically typed:

VarDemo.java
import java.util.HashMap;

public class VarDemo {
    public static void main(String[] args) {
        var count = 10;                       // inferred as int
        var name = "Java";                    // inferred as String
        var scores = new HashMap<String, Integer>();
        scores.put(name, count);

        // The type is still fixed at compile time:
        // count = "ten";                     // compile error
        System.out.println(scores + " -> count is an int: " + ((Object) count instanceof Integer));
    }
}
Output
{Java=10} -> count is an int: true

var works only for local variables with an initializer (and in for loops and lambda parameters), not for fields, method parameters or return types.

10. Primitives vs References in Memory

A local int variable holds its value directly in the method's stack frame. A local String variable holds a reference in the stack frame, while the string object lives on the heap. That is why copying an int copies the value, but copying an object reference makes two variables share one object. See Classes and Objects.

11. Common Mistakes

  • Writing long big = 9000000000; without L: compile error, the literal is too large for an int.
  • Writing float f = 1.5;: compile error, 1.5 is a double; write 1.5f.
  • Expecting 5 / 2 to be 2.5.
  • Using double for currency.
  • Comparing Integer objects with ==.
  • Ignoring overflow in calculations such as int milliseconds or large products.

12. Summary

  • Java has 8 primitive types with fixed sizes and default values, and reference types for objects.
  • int and double are the default choices; use long for large numbers and BigDecimal for money.
  • Widening is automatic; narrowing needs a cast and can lose data or overflow.
  • Wrapper classes let primitives be used as objects through autoboxing.
  • var infers local variable types without making Java dynamically typed.

Related Tutorials

Reference: Java Language Specification, Chapter 4: Types, Values, and Variables