Tutorials

The Java Tutorial: Primitive Data Types

Key Takeaways

  • Java has eight primitive types: byte, short, int, long, float, double, boolean, and char.
  • Start with int for ordinary whole numbers and double for approximate calculations; choose other types when their range or representation fits your data.
  • A char is one UTF-16 code unit, not necessarily a complete Unicode character.
  • Smaller numeric types aren't automatically faster, and their bit widths don't tell you the total memory occupied by fields or arrays.
  • Use BigDecimal or an explicitly scaled integer representation when monetary arithmetic must preserve exact decimal values.

Java primitive data types represent numeric, logical, and UTF-16 code-unit values without being objects. Choose among them according to the values you need to represent, the calculations you'll perform, and the requirements of the API or file format you're using.

You don't construct primitive values with new. That distinction doesn't mean primitives always live on the stack or that every primitive operation is faster than an object-based alternative; see Java heap vs stack for the separate question of runtime storage.

Java Primitive Data Types: Size and Range

The Java Language Specification defines these value domains. Integer ranges below are exact and inclusive; floating-point limits are approximate maximum finite magnitudes, not guarantees of decimal accuracy.

TypeValue widthRange or domainTypical use
byte8 bits-128 to 127Binary buffers and protocol data
short16 bits-32,768 to 32,767Formats requiring signed 16-bit values
int32 bits-2,147,483,648 to 2,147,483,647Indexes, counts, and ordinary integer arithmetic
long64 bits-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807Larger counters, identifiers, and numeric timestamps
float32 bitsFinite values up to approximately ±3.4 × 1038; also signed zero, infinities, and NaNSingle-precision numeric data
double64 bitsFinite values up to approximately ±1.8 × 10308; also signed zero, infinities, and NaNGeneral-purpose approximate calculations
booleanStorage size not specified by the Java languagetrue or falseConditions and flags
char16 bits0 to 65,535; one UTF-16 code unitCode-unit processing and simple character literals

Java specifies two's complement as the signed binary representation for byte, short, int, and long. Each has one more negative value than positive values. There are no separate unsigned counterparts to those four types; char is unsigned, but its intended role is UTF-16 text representation.

The Eight Primitive Types, with Examples

Examples labeled as method fragments belong inside a method such as main. Complete programs include their own class and entry point.

byte

When should you use byte?

Use byte for raw binary data, such as file contents and network buffers, or when a format explicitly requires an 8-bit value. It remains signed even when its bits represent an unsigned protocol value.

Example

// Method fragment:
byte myByte = 120;

byte[] list1 = new byte[10]; // 10 bytes of element payload
int[] list2 = new int[10];   // 40 bytes of element payload

byte raw = (byte) 0xFF;
int unsignedValue = Byte.toUnsignedInt(raw); // 255

Those payload calculations exclude array overhead. Byte.toUnsignedInt(byte) interprets the byte's bits as a value from 0 through 255; it doesn't introduce an unsigned primitive type. For allocation and indexing examples, continue with Java arrays.

short

When should you use short?

Choose short when an API or binary format requires a signed 16-bit value. Large primitive arrays can also justify its narrower representation, provided every value fits; don't choose it merely because a small local variable looks wasteful as an int.

Example

// Method fragment:
short myShort = 2000;

short[] list1 = new short[10]; // 20 bytes of element payload
int[] list2 = new int[10];     // 40 bytes of element payload

int

When should you use int?

Use int as your starting point for whole-number arithmetic when the range is sufficient. Java array indexes use int, making it a natural choice for indexes and bounded counts; its maximum value doesn't guarantee that an array of that length can be allocated.

Example

// Method fragment:
int myInt = 5_000_000;
int flags = 0b1010;
int mask = 0xFF;

A literal is a value written directly in source code. Integer literals have type int unless suffixed with L or l; underscores between digits improve readability, while 0b and 0x introduce binary and hexadecimal literals.

long

When should you use long?

Use long for counters or identifiers that may exceed the int range. It's also useful for numeric timestamps, but document their unit and meaning rather than relying on the type alone.

Example

// Method fragment:
long myLong = 3000000000L;

The trailing L makes this a long literal. Without it, the decimal literal is too large for int, even though the destination variable is long. Prefer uppercase L because lowercase l resembles the digit 1.

float

When should you use float?

Use float when an API or stored numeric format requires single precision, or when reducing large numeric-array payloads justifies reduced precision. It uses IEEE 754 binary32 representation with 24 bits of significand precision—roughly seven significant decimal digits, not seven decimal places.

Example

// Method fragment:
float myFloat = 144.5F;

The F suffix makes the literal a float. Without F or f, a floating-point literal has type double.

double

When should you use double?

Use double for general-purpose approximate calculations, including many scientific calculations. Its IEEE 754 binary64 representation provides 53 bits of significand precision—roughly 16 significant decimal digits—and a wider magnitude range than float.

Example

// Method fragment:
double myDouble = 1.123456789;

float smaller = 16_777_216F;
double larger = 16_777_216D;
System.out.println(smaller + 1F == smaller); // true
System.out.println(larger + 1D == larger);   // false

At this magnitude, float can't preserve the increment of one, while double can. Precision describes how finely values can be distinguished; range describes how large or small their magnitudes can be. Neither means exact decimal representation, as the Double documentation's decimal-conversion discussion explains. The D suffix is optional.

boolean

When should you use boolean?

Use boolean for conditions, flags, and toggles. It accepts only true and false; Java doesn't treat numbers as implicit truth values, so write a comparison rather than if (1).

Example

// Method fragment:
boolean myBool = true;
int available = 3;

if (myBool && available > 0) {
    System.out.println("Ready");
}

The && operator evaluates its right operand only when its left operand is true. See Java control flow statements for conditions and loops. Don't infer a one-bit storage allocation from boolean's two possible values.

char

When should you use char?

Use char for a UTF-16 code unit, including simple literals such as 'C'. A Unicode code point is a numbered position in Unicode; supplementary code points require two UTF-16 code units, called a surrogate pair.

Example

// Save as UnicodeDemo.java
public class UnicodeDemo {
    public static void main(String[] args) {
        char myChar = 'C';
        String text = "C\uD83D\uDE00";

        System.out.println(myChar);                 // C
        System.out.println(text.length());          // 3 code units
        System.out.println((int) text.charAt(1));    // 55357: high surrogate
        System.out.println(text.codePointAt(1));    // 128512: U+1F600
        System.out.println(text.codePoints().count()); // 2 code points
    }
}
U+0043  --> [0043]        --> one char
U+1F600 --> [D83D][DE00]  --> two chars: a surrogate pair

String.charAt(int) returns a code unit; codePointAt(int) returns an int code point and combines a valid surrogate pair starting at that index. The index still counts code units. Use codePoints() when iterating by code point instead of splitting supplementary characters.

Integer Arithmetic: Promotion, Overflow, and Division

Why byte and short arithmetic produces int

For addition of byte or short operands, Java promotes the operands to int, so the result is also int. A small constant assignment such as byte b = 1; is allowed because the constant fits, but assigning the expression b + 1 back to byte requires narrowing.

// Save as PromotionDemo.java
public class PromotionDemo {
    public static void main(String[] args) {
        byte b = 1;
        // b = b + 1; // Compile error: the result is int.
        b = (byte) (b + 1);
        System.out.println(b); // 2

        b += 1; // Includes an implicit narrowing conversion.
        System.out.println(b); // 3

        b = 127;
        b += 1;
        System.out.println(b); // -128: narrowing discards higher bits.
    }
}

A cast requests a conversion; it doesn't validate that a value fits. The specification covers numeric promotion and narrowing conversions, while its expression rules explain the implicit conversion in compound assignment.

Overflow is silent; integer division truncates

Ordinary integer addition and multiplication don't throw on overflow. Integer division truncates toward zero: 7 / 2 is 3, and -7 / 2 is -3. Integer division by zero throws ArithmeticException.

// Method fragment:
int count = 50_000;
long safeSquare = (long) count * count; // 2,500,000,000
long tooLate = count * count;          // -1,794,967,296

double truncated = 7 / 2; // 3.0: integer division happens first
double fractional = 7.0 / 2; // 3.5

Widen an operand before arithmetic when you need a long result; assigning an already-overflowed int result to long can't repair it. For checked integer arithmetic, Math.addExact and Math.multiplyExact have int and long overloads that throw ArithmeticException on overflow.

Floating-Point Precision and Exact Monetary Arithmetic

Computed floating-point values need deliberate comparisons

// Method fragment:
double sum = 0.1 + 0.2;
System.out.println(sum);        // 0.30000000000000004
System.out.println(sum == 0.3); // false

Many decimal fractions can't be represented exactly in binary floating point. For approximate computed results, choose an absolute or relative comparison tolerance appropriate to your domain; there isn't one universal epsilon.

  • 0.0 / 0.0 produces NaN, meaning Not-a-Number. NaN is unequal to every value, including itself; test it with Double.isNaN(value).
  • 1.0 / 0.0 produces positive infinity, rather than the exception associated with integer division by zero.
  • 0.0 == -0.0 is true, but dividing 1.0 by those zeros produces positive and negative infinity respectively.

Use BigDecimal with explicit construction and rounding

For decimal monetary values requiring exact decimal representation, use BigDecimal, or store an integer number of explicitly defined minor units. Construct decimal input from a string: new BigDecimal("0.1") represents that decimal exactly, whereas new BigDecimal(0.1) preserves the double's binary approximation.

// Save as MoneyDemo.java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class MoneyDemo {
    public static void main(String[] args) {
        BigDecimal total = new BigDecimal("10.00");
        BigDecimal people = BigDecimal.valueOf(3L);
        BigDecimal share = total.divide(people, 2, RoundingMode.HALF_EVEN);
        System.out.println(share); // 3.33
    }
}

The BigDecimal API defines construction, scale, and division behavior. Here, scale 2 requests two digits after the decimal point; the rounding mode is an explicit example policy, not a universal currency rule. Exact division without a rounding policy throws ArithmeticException when the quotient has a nonterminating decimal expansion.

If you must convert an existing double, BigDecimal.valueOf(double) uses its canonical decimal string representation, but it can't undo earlier floating-point rounding. For real payment logic, also decide how to allocate any remainder left by rounding individual shares.

Default Values: Fields and Arrays versus Local Variables

Fields and array elements receive default values: zero for byte, short, int, and long; positive zero for float and double; '\u0000' for char; and false for boolean. Local variables must be definitely assigned before use—the compiler must be able to establish that an assignment has occurred on every path reaching the read.

// Method fragment:
int[] counts = new int[2];
System.out.println(counts[0]); // 0

int local;
// System.out.println(local); // Compile error: not definitely assigned.
local = 1;
System.out.println(local); // 1

These primitive initialization rules also explain why a newly allocated numeric array starts with zeros even when you haven't assigned its elements.

Primitive Types versus Wrapper Classes

Each primitive has a corresponding wrapper class: byte → Byte, short → Short, int → Integer, long → Long, float → Float, double → Double, boolean → Boolean, and char → Character. Boxing converts a primitive value to a wrapper reference; unboxing extracts the primitive value.

// Method fragment:
Integer boxed = 42; // Autoboxing
int unboxed = boxed; // Unboxing

Integer missing = null;
// int value = missing; // Would throw NullPointerException.

Wrapper references can be null; primitive variables cannot hold null. Unboxing a null reference throws NullPointerException. Use primitives when absence isn't part of the value model, and choose wrappers when an API requires references or you deliberately need a nullable value.

Primitives cannot be generic type arguments: use List<Integer>, not List<int>. Continue with Java wrapper classes and autoboxing and Java generics and type inference.

Common Mistakes

  • Choosing the smallest type automatically: choose a range that fits your data and intermediate results, then measure memory or performance if it matters.
  • Casting after multiplication: convert an operand before the operation; a wider destination doesn't change how the expression was evaluated.
  • Confusing precision with decimal places: significant digits apply across the whole value, not just the fractional part.
  • Treating every char as a complete character: use code-point-aware operations when supplementary Unicode characters are possible.

FAQ

What are the eight primitive data types in Java?

The eight Java primitive types are byte, short, int, long, float, double, boolean, and char. String and BigDecimal are reference types, not primitives.

When should I use int versus long in Java?

Use int when values and intermediate calculations fit its range of -2,147,483,648 to 2,147,483,647. Use long for larger values, and widen an operand before arithmetic if the calculation could overflow int.

Why does Java promote byte and short arithmetic to int?

Java's numeric promotion rules perform addition of byte or short operands using int arithmetic. Assigning the result back requires a cast, while compound assignment such as b += 1 includes an implicit narrowing conversion that can discard information.

Is char in Java one Unicode character?

A char is one 16-bit UTF-16 code unit, not necessarily a complete Unicode code point. Supplementary code points require a surrogate pair; use String.codePointAt or String.codePoints when processing code points.

Why should I not use float or double for currency?

Binary floating-point types cannot exactly represent many decimal monetary values. Use BigDecimal constructed from decimal strings, or an integer minor-unit representation, and define rounding and scale explicitly.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me