Command Palette

Search for a command to run...

PHASE 1Beginner ~28 min· topic 2 of 14

Topic 1.2

The Eight Primitive Types

In one line

Java has exactly eight built-in primitive types: four for whole numbers (byte, short, int, long), two for decimals (float, double), one for characters (char) and one for true/false (boolean). Each has a fixed size and range on every computer, which is a big part of why Java programs behave the same everywhere.

Think of it like this

Cups of different sizes. A tiny espresso cup, a tea cup, a mug and a big jug. The bigger the cup, the more it holds, but the more cupboard space it takes. Java's whole-number types are cups of 1, 2, 4 and 8 bytes: byte, short, int and long. You pick the smallest cup that will never overflow, and for everyday counting that's the int mug.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Bit
The smallest unit of memory: a single 0 or 1.
Byte
A group of 8 bits. It can store 256 different patterns. Also the name of Java's smallest integer type.
Primitive type
One of Java's eight built-in value types. A primitive variable holds the value directly, not a reference to an object.
Range
The smallest and largest value a type can hold, decided by how many bits it has.
Signed
Able to hold negative as well as positive numbers. All Java integer types except char are signed.
Literal
A value written directly in code, like 42, 3.5, 'A', true or "hi".
Wrapper class
An object version of a primitive, such as Integer for int. It holds useful constants and methods and lets numbers go into collections.
Default value
The value a field or array element gets automatically if you don't set one: 0, 0.0, false, '\u0000' or null.

Step by step

01Bits decide everything

A type with n bits can represent exactly 2ⁿ different patterns. 8 bits give 256 patterns, so a byte can hold 256 different values. Java spends half of them on negative numbers: -128 to 127 (zero takes one of the 'positive' slots, which is why the top is 127, not 128).

The same rule scales up: short has 2¹⁶ = 65,536 values, int has 2³² (about 4.29 billion, so roughly ±2.1 billion), long has 2⁶⁴. How negative numbers are actually encoded (two's complement) is the subject of Topic 1.3.

Bits decide everythingdiagram
Rendering diagram…

02The full table

byte: 1 byte, -128 to 127. short: 2 bytes, -32,768 to 32,767. int: 4 bytes, -2,147,483,648 to 2,147,483,647. long: 8 bytes, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

float: 4 bytes, up to about 3.4 × 10³⁸, about 7 significant digits. double: 8 bytes, up to about 1.8 × 10³⁰⁸, about 15 to 16 significant digits.

char: 2 bytes, 0 to 65,535 (unsigned). boolean: true/false; the specification deliberately doesn't fix its size. HotSpot stores a boolean field or array element in one byte and treats it as an int during calculations.

You never need to memorise the big numbers: the wrapper classes print them for you (first runnable example below).

03Literals have types too

The number 42 in your code is an int literal. 42L is a long. 3.5 is a double, 3.5f is a float. 'A' is a char, true is a boolean.

The compiler checks that each literal fits where you put it. byte b = 100; is fine because 100 fits in a byte, even though 100 is an int literal: Java allows this narrowing for constant values that fit. byte b = 200; fails, because 200 doesn't fit.

A whole-number literal bigger than the int range is an error on its own, before Java even looks at the variable: long d = 3000000000; fails with *integer number too large*. Add L to make it a long literal. Use a capital L; a lowercase l looks like the digit 1.

Main.javawhole filejava
byte small = 100;                 // ok: constant 100 fits in a byte
long big = 8_100_000_000L;        // L makes it a long literal
float ratio = 0.75f;              // f makes it a float literal
double precise = 0.75;            // decimal literals are double by default
char letter = 'A';
boolean ready = true;

04Primitives vs objects in memory

An int local variable is 4 bytes in a stack-frame slot, holding the number. An Integer is an object on the heap: on a typical 64-bit HotSpot JVM it has a 12-byte header plus the 4-byte value, rounded to 16 bytes, and you also need a reference (4 or 8 bytes) to reach it.

So an array of a million ints is about 4 MB, while a list of a million Integer objects is roughly 16 MB of objects plus 4 MB or more of references, and reading them means following a pointer for each one. This is why primitives exist at all: Java's designers kept them for speed and memory, at the cost of making the language not 'everything is an object'.

Primitives vs objects in memorydiagram
Rendering diagram…

05Default values: fields yes, locals no

Fields (variables declared in a class, outside any method) and array elements always start with a default: 0 for integers, 0.0 for decimals, '\u0000' (character code 0) for char, false for boolean, and null for references.

Local variables get no default at all, as Topic 1.1 showed. The difference exists because the JVM zeroes the memory of every new object and array, which is cheap and safe, while forcing you to initialize locals catches real bugs.

06Choosing a type

Use int for ordinary counts, indexes and quantities. It's the type Java's arithmetic is built around: byte and short values are promoted to int before any arithmetic anyway (Topic 1.6).

Use long when values can pass about 2 billion: file sizes, database IDs, milliseconds since 1970 (System.currentTimeMillis() returns a long), populations.

Use double for measurements and science. Use BigDecimal (Topic 1.4) for money, never float or double.

Use byte for raw binary data (file contents, network packets as byte[]) and short/float mainly to save memory in very large arrays. Use boolean for yes/no flags, and char for single characters.

Try it yourself

  1. 1

    Find the edge of a byte

    In the third example change byte age = 12; to byte age = 127; and run: it works. Now try 128. Predict the compiler message before you run it.

    terminal
    $ javac Main.java
    ── expected output ──
    Main.java:3: error: incompatible types: possible lossy conversion from int to byte
    byte age = 128;
    ^
    1 error
  2. 2

    Remove a suffix

    Delete the L from 8_100_000_000L and compile. Then delete the f from the float line. Each gives a different error: one says the literal itself is too large for an int, the other says a double can't be squeezed into a float silently.

  3. 3

    Measure a type yourself

    Add System.out.println(Integer.BYTES * 1_000_000); to any example. It prints how many bytes one million int values need in an array: 4000000.

Code & diagrams

Every numeric type's size and range New tab

`3.4028235E38` is scientific notation: 3.4028235 × 10³⁸.

Sign in to run this example in your browser.

Expected output

byte   8 bits  -128 to 127
short  16 bits  -32768 to 32767
int    32 bits  -2147483648 to 2147483647
long   64 bits  -9223372036854775808 to 9223372036854775807
char   16 bits  0 to 65535
float  32 bits  max 3.4028235E38
double 64 bits  max 1.7976931348623157E308
Default values of fields New tab
Sign in to run this example in your browser.

Expected output

byte=0 short=0 int=0 long=0
float=0.0 double=0.0
char code=0 boolean=false
String=null
Literal suffixes and float precision New tab
Sign in to run this example in your browser.

Expected output

12 2026 8100000000 A true
double pi: 3.141592653589793
float  pi: 3.1415927

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put a decimal literal into a float

Write float price = 3.14;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: incompatible types: possible lossy conversion from double to float
float price = 3.14;
^
1 error

Break #2

Write a big number without L

Write long distance = 3000000000;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: integer number too large
long distance = 3000000000;
^
1 error

Myth vs fact

Myth

int is 16 bits on some machines and 32 on others.

Fact

That's C. In Java every primitive has the same size and range on every platform, fixed by the language specification.

Myth

Using byte or short makes arithmetic faster.

Fact

Arithmetic on them is done in int anyway (they are promoted first). Smaller types only save memory in large arrays.

Myth

String is a primitive type.

Fact

String is a class. Its variables hold references to objects. It just has special literal syntax ("hi") and + support.

Myth

A boolean takes 1 bit.

Fact

The JVM specification doesn't fix the size. HotSpot uses one byte per boolean field or array element; java.util.BitSet is the tool for one bit per flag.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The JVM instruction set has almost no byte/short/char arithmetic: values are loaded as int (baload, caload, saload sign- or zero-extend) and computed with iadd, imul and friends. Only stores narrow them (i2b, i2s, i2c).

  • ▸

    Field layout in HotSpot is not declaration order: the JVM packs fields by size (longs/doubles, then ints, then shorts/chars, then bytes/booleans) to reduce padding. Tools like JOL (Java Object Layout) show the real layout and object sizes.

  • ▸

    With compressed oops (the default for heaps under 32 GB) a reference is 4 bytes; above that it's 8, which is why a 33 GB heap can hold fewer objects than a 31 GB one.

  • ▸

    Project Valhalla (in progress, not in Java 25) aims to add value classes so that user-defined types can be stored flat like primitives. Until then, primitives and primitive arrays are the only way to get dense, pointer-free data.

Remember this

  1. 1

    A primitive is a plain value, not an object. An int variable holds the number itself in 32 bits; there is nothing else attached to it. Everything else in Java (Strings, lists, your own classes) is an object reached through a reference.

  2. 2

    The four integer types are byte (8 bits, -128 to 127), short (16 bits, -32,768 to 32,767), int (32 bits, about ±2.1 billion) and long (64 bits, about ±9.2 quintillion). They are all signed: they can hold negative numbers. Java has no unsigned int keyword (Java 8 added helper methods like Integer.toUnsignedString instead).

  3. 3

    The two floating-point types are float (32 bits, about 7 significant digits) and double (64 bits, about 15 to 16 significant digits). They store approximations, which Topic 1.4 explains in depth. Use double unless you have a measured reason not to.

  4. 4

    char (16 bits, 0 to 65,535) holds one UTF-16 code unit, usually one character like 'A' or 'ज'. boolean holds only true or false and is not a number. Topic 1.5 covers both.

  5. 5

    Literal defaults matter: a whole number written in code, like 42, is an int; a number with a decimal point, like 3.5, is a double. To get a long literal add L (8_100_000_000L); for a float add f (0.75f). Without the suffix, float f = 3.14; is a compile error.

  6. 6

    Sizes are the same on every platform, unlike C where int might be 16 or 32 bits. The Java Language Specification fixes them, and each type has a wrapper class (Byte, Short, Integer, Long, Float, Double, Character, Boolean) holding constants such as Integer.MAX_VALUE and helpers such as Integer.parseInt. Wrappers are objects; you'll meet them properly with collections in Phase 9.

Explain it without notes

01

Name the eight primitive types with their sizes, and say which is the default for whole numbers and which for decimals.

02

Why does long x = 3000000000; fail to compile even though 3 billion fits in a long?

03

Why does Java have primitives at all, if everything else is an object?

04

Which values do fields and array elements get by default, and why don't local variables get them?

Practice

01

Declare well-chosen variables for: a person's age (14), the number of stars in the Milky Way (100 billion), a temperature (36.6), a flag saying whether a door is locked, and a grade letter. Print them on one line.

02

Print how many bytes one million values of byte, int, long and double would need in an array, using the BYTES constants.

03

Print one third as a float and as a double and count how many correct digits each shows.

Trade-offs

  • ↔

    long instead of int doubles memory per value but removes most overflow risk; for IDs, timestamps and sizes the safety is nearly always worth it.

  • ↔

    float halves memory compared with double (useful in huge arrays, graphics and machine learning) but keeps only about 7 digits, which is too few for most business calculations.

  • ↔

    Primitives are fast and compact but can't be null and can't go into collections directly; wrappers can, at the cost of memory and an extra pointer per value.

Done when you can

  • I can list the eight primitive types with their bit sizes and rough ranges.

  • I know a plain whole literal is int, a plain decimal literal is double, and when to use L and f.

  • I can explain the memory difference between int and Integer.

  • I know which variables get default values and what those defaults are.

  • I can choose a sensible type for counts, big numbers, measurements, money and flags.