Back to Blog
Java

Java Primitive Data Types: Size, Range, and Usage

java primitive data types: Understand Java's eight primitive data types, their sizes, default values, memory behavior, and when to use them over wrapper classes.

primitive typesJava type systemautoboxingmemory managementJava performance
Diagram showing the eight Java primitive data types with their sizes and memory footprint

java primitive data types requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Java's type system has two categories: primitive types and reference types. The eight primitive data types are byte, short, int, long, float, double, char, and boolean. They are the only values stored directly in variables, not as objects. Understanding their sizes, ranges, and runtime behavior is essential for writing correct and efficient Java code.

The Eight Primitive Types and Their Ranges

Each primitive type has a fixed size defined by the Java language specification. These sizes do not vary across platforms, which is one reason Java is portable.

TypeSizeRange (inclusive)Typical Use
byte8 bits-128 to 127Small integer values
short16 bits-32,768 to 32,767Smaller integer ranges
int32 bits-2^31 to 2^31-1Default integer type
long64 bits-2^63 to 2^63-1Large integer values
float32 bitsApprox. ±3.4e38, 6-7 significant digitsSingle-precision decimal
double64 bitsApprox. ±1.8e308, 15-16 significant digitsDefault decimal type
char16 bits0 to 65,535 (Unicode code units)Single Unicode character
booleannot precisely definedtrue or falseLogical flags

Note that char is an unsigned type, unlike the signed integer types. Also, boolean's size is not specified; it depends on the JVM implementation, but it is typically treated as a single byte in arrays and as an int in local variables.

Default Values and Initialization

When a primitive field is declared without an explicit initializer, the JVM assigns a default value. This is different from local variables, which must be initialized before use.

TypeDefault Value
byte0
short0
int0
long0L
float0.0f
double0.0d
char'\u0000'
booleanfalse

For example:

public class DefaultValues { int count; // default 0 boolean active; // default false char letter; // default '\u0000' }

Local variables do not receive default values. The compiler rejects code that reads a local variable before assignment:

public void example() { int x; System.out.println(x); // compile-time error: variable x might not have been initialized }

Wrapper Classes and Autoboxing

Each primitive type has a corresponding wrapper class in the java.lang package: Byte, Short, Integer, Long, Float, Double, Character, and Boolean. Wrappers allow primitives to be used where objects are required, such as in collections like List<Integer>.

Autoboxing and unboxing convert between primitives and wrappers automatically:

Integer boxed = 42; // autoboxing: int to Integer int unboxed = boxed; // unboxing: Integer to int

This convenience hides an allocation cost. Every autoboxing operation creates a new Integer object unless the value falls within the Integer cache range (-128 to 127). That cache is used for Integer.valueOf, but autoboxing also uses it. For other values, a new object is allocated each time. In performance-sensitive loops, this can create unnecessary garbage.

for (int i = 0; i < 1_000_000; i++) { Integer value = i; // autoboxing; values outside cache allocate new objects }

Prefer primitives in hot paths and use wrappers only when the API requires an object.

Memory and Performance Considerations

Primitive variables store their value directly in the stack frame (for local variables) or as part of the object layout (for instance fields). Reference variables, by contrast, store a pointer to an object on the heap. This difference matters for memory footprint and access speed.

Consider an array of int versus an array of Integer. An int[] stores contiguous 32-bit values. An Integer[] stores references to Integer objects, each of which has an object header and a 32-bit value. On a typical 64-bit JVM with compressed oops, an Integer object may consume 16 bytes or more, plus the reference in the array. For large arrays, the difference is substantial.

int[] ints = new int[1_000_000]; // ~4 MB Integer[] integers = new Integer[1_000_000]; // ~4 MB for references + ~16 MB for objects

This is why primitive arrays are often preferred for numeric data.

Common Pitfalls with Primitive Types

Integer Overflow

Arithmetic on int and long can overflow silently. For example:

int max = Integer.MAX_VALUE; int overflowed = max + 1; // wraps to -2147483648

Java does not detect overflow. Use Math.addExact or Math.multiplyExact when overflow should throw an exception.

Floating-Point Precision

float and double are binary floating-point types. They cannot represent many decimal fractions exactly. For monetary calculations, use BigDecimal instead.

double a = 0.1; double b = 0.2; System.out.println(a + b); // 0.30000000000000004

Char Is Not a Full Character

char is a UTF-16 code unit, not a full Unicode code point. Supplementary characters (like emoji) require two char values. When iterating over string characters, use codePointAt or String.codePoints().

Choosing Between Primitive and Reference Types

Use primitives when:

  • The value has a natural numeric or boolean meaning.
  • Performance and memory matter, especially in collections or arrays.
  • You want to avoid null values. Primitives cannot be null; a missing value must be represented differently, such as a sentinel value or an Optional.

Use wrappers when:

  • The value must be stored in a generic collection like List<Integer>.
  • You need to represent an absent value as null.
  • The value participates in generic type parameters, since generics cannot use primitives.

For example, a method that returns an optional integer might use Integer to allow null:

public Integer findIndex(String key) { // return null if not found }

But if the absence is common, consider OptionalInt to avoid boxing.

Primitive Types in Collections and Streams

Generic collections cannot hold primitives directly. List<int> is not valid Java. You must use List<Integer>, which incurs boxing overhead. For performance-sensitive code, consider specialized libraries like Trove or use primitive arrays.

The Stream API includes specialized streams for int, long, and double: IntStream, LongStream, and DoubleStream. These avoid boxing when processing numeric data.

int sum = IntStream.range(1, 100) .filter(n -> n % 2 == 0) .sum();

OptionalInt is the primitive counterpart of Optional<Integer>. It avoids boxing when a result may be absent.

Primitive Types in Switch Statements and Pattern Matching

Java 7 introduced switching on String, but primitive types have always been allowed in switch. You can switch on int, char, byte, short, and enum. You cannot switch on long, float, double, or boolean. This is a long-standing limitation.

switch (dayOfWeek) { case 1: // Monday break; case 2: // Tuesday break; default: break; }

In Java 21, pattern matching for switch works with reference types, but primitives are still not supported as selector expressions beyond the traditional integral types.

When to Use Primitive Arrays vs. Collections

The decision between primitive arrays and collections often comes down to memory, speed, and API needs. If you are working with a fixed-size numeric dataset, an array is the most efficient choice. If you need dynamic resizing or the convenience of collection methods, ArrayList<Integer> is easier but costs memory and boxing overhead.

For large numeric datasets, consider using a primitive array and writing small helper methods for operations like sum, average, or filtering. The JVM can optimize array access more aggressively than boxed collections.

public static double average(int[] values) { long sum = 0; for (int v : values) { sum += v; } return (double) sum / values.length; }

This avoids creating Integer objects and keeps the data compact. When the dataset is small or the code is not performance-critical, the simplicity of a collection may be worth the overhead.

java primitive data types: Practical Usage and Code Examples | RYUSLOG DEV