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
charare signed. - Literal
- A value written directly in code, like
42,3.5,'A',trueor"hi". - Wrapper class
- An object version of a primitive, such as
Integerforint. 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'ornull.
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.
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.
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'.
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
Find the edge of a byte
In the third example change
byte age = 12;tobyte age = 127;and run: it works. Now try128. 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 bytebyte age = 128;^1 error - 2
Remove a suffix
Delete the
Lfrom8_100_000_000Land compile. Then delete theffrom thefloatline. Each gives a different error: one says the literal itself is too large for anint, the other says adoublecan't be squeezed into afloatsilently. - 3
Measure a type yourself
Add
System.out.println(Integer.BYTES * 1_000_000);to any example. It prints how many bytes one millionintvalues need in an array:4000000.
Code & diagrams
`3.4028235E38` is scientific notation: 3.4028235 × 10³⁸.
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.7976931348623157E308Expected output
byte=0 short=0 int=0 long=0
float=0.0 double=0.0
char code=0 boolean=false
String=nullExpected output
12 2026 8100000000 A true
double pi: 3.141592653589793
float pi: 3.1415927Break 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;.
Break #2
Write a big number without L
Write long distance = 3000000000;.
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/chararithmetic: values are loaded asint(baload,caload,saloadsign- or zero-extend) and computed withiadd,imuland 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
A primitive is a plain value, not an object. An
intvariable 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
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) andlong(64 bits, about ±9.2 quintillion). They are all signed: they can hold negative numbers. Java has nounsigned intkeyword (Java 8 added helper methods likeInteger.toUnsignedStringinstead). - 3
The two floating-point types are
float(32 bits, about 7 significant digits) anddouble(64 bits, about 15 to 16 significant digits). They store approximations, which Topic 1.4 explains in depth. Usedoubleunless you have a measured reason not to. - 4
char(16 bits, 0 to 65,535) holds one UTF-16 code unit, usually one character like'A'or'ज'.booleanholds onlytrueorfalseand is not a number. Topic 1.5 covers both. - 5
Literal defaults matter: a whole number written in code, like
42, is anint; a number with a decimal point, like3.5, is adouble. To get alongliteral addL(8_100_000_000L); for afloataddf(0.75f). Without the suffix,float f = 3.14;is a compile error. - 6
Sizes are the same on every platform, unlike C where
intmight 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 asInteger.MAX_VALUEand helpers such asInteger.parseInt. Wrappers are objects; you'll meet them properly with collections in Phase 9.
Explain it without notes
Name the eight primitive types with their sizes, and say which is the default for whole numbers and which for decimals.
Why does long x = 3000000000; fail to compile even though 3 billion fits in a long?
Why does Java have primitives at all, if everything else is an object?
Which values do fields and array elements get by default, and why don't local variables get them?
Practice
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.
Print how many bytes one million values of byte, int, long and double would need in an array, using the BYTES constants.
Print one third as a float and as a double and count how many correct digits each shows.
Trade-offs
- ↔
longinstead ofintdoubles memory per value but removes most overflow risk; for IDs, timestamps and sizes the safety is nearly always worth it. - ↔
floathalves memory compared withdouble(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
nulland 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 isdouble, and when to useLandf.I can explain the memory difference between
intandInteger.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.