HomeGuidesJava
Java8 min readCode verified

Java's Primitive Types, and the Four That Trip Everyone Up

Eight types, about twenty minutes of memorization — and four behaviors that will cost you marks until someone points them out. Every result on this page was executed, not remembered.

The eight primitives#

Java has exactly eight primitive types. They are not objects, they hold a value directly, and they are the only types that work this way — everything else in Java, String included, is a reference type.

TypeSizeRangeLiteral
byte8 bits−128 to 127(byte) 100
short16 bits−32768 to 32767(short) 1000
int32 bits−2147483648 to 214748364742
long64 bits−9223372036854775808 to 922337203685477580742L
float32 bitsabout 7 decimal digits of precision3.14f
double64 bitsabout 15 decimal digits of precision3.14
char16 bits0 to 65535 (a character code)'A'
booleantrue or false onlytrue

Two of those rows deserve a second look. int is the default for whole numbers and double is the default for decimals — which is exactly why the next two errors happen.

Literals have types before they are assigned#

This looks like it should obviously work, and it does not compile:

long big = 10000000000;
Big.java:3: error: integer number too large
        long big = 10000000000;
                   ^
1 error

The variable is a long, so what is too large? The literal is. Java reads 10000000000 as an int before it ever looks at the left-hand side, and ten billion does not fit in an int. The L suffix tells the compiler to read it as a long from the start:

long big = 10000000000L;   // compiles

The same thing happens with float, in the other direction:

float f = 3.14;
Flt.java:3: error: incompatible types: possible lossy conversion from double to float
        float f = 3.14;
                  ^
1 error

3.14 is a double, and squeezing a 64-bit value into a 32-bit variable could lose precision, so Java refuses. Write 3.14f.

The pattern behind both

Java will silently widen a value into a larger type, but never silently narrow one into a smaller type. Narrowing needs your explicit permission, in the form of a suffix or a cast. Even this fails, because a variable's value is not known at compile time:

int x = 5;
byte b = x;   // error: incompatible types: possible lossy conversion from int to byte

Trap 1 — integer division throws away the remainder#

If both operands are whole numbers, Java does whole-number division. There is no rounding involved:

System.out.println(7 / 2);
System.out.println(7 % 2);
System.out.println(7.0 / 2);
3
1
3.5

7 / 2 is 3, not 3.5 and not 4. The % operator recovers the remainder that division discarded.

The fix is to make at least one operand floating-point — but where you put the cast decides whether it works:

System.out.println((double) 7 / 2);     // cast first, then divide
System.out.println((double) (7 / 2));   // divide first, then cast
3.5
3.0

In the second line the damage is already done: 7 / 2 evaluated to the int 3, and casting 3 to a double gives 3.0. Casting cannot recover information that was already discarded.

Trap 2 — overflow is silent#

System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
2147483647
-2147483648

Adding one to the largest possible int produces the smallest possible int. No exception, no warning, no crash — the value wraps around like a car odometer rolling over, and the program keeps going with a wrong number.

This is why counters, running totals, and anything measured in milliseconds usually want long rather than int.

Trap 3 — decimals are not exact#

System.out.println(0.1 + 0.2);
System.out.println(0.1 + 0.2 == 0.3);
0.30000000000000004
false

double and float store values in binary. One tenth cannot be written exactly in binary for the same reason one third cannot be written exactly in decimal — the digits never stop. What gets stored is the nearest available value, and the tiny errors accumulate.

Consequence

never compare floating-point values with ==. Compare the size of the difference against a small tolerance instead: Math.abs(a - b) < 1e-9. And never use double for money — count whole cents in an int or long.

Trap 4 — char is a number wearing a costume#

System.out.println('A' + 1);
System.out.println((char) ('A' + 1));
66
B

A char holds a number identifying a character; 'A' is 65. Arithmetic on it therefore produces a number, and Java promotes the result to int — which is why the first line prints 66 and not 'B'. Casting the result back to char gives the letter.

This is genuinely useful once you expect it. ch - 'a' converts a lowercase letter to its position in the alphabet, and (char) ('a' + n) converts a position back to a letter.

Casting truncates, it does not round#

System.out.println((int) 9.99);
System.out.println((int) -9.99);
9
-9

A cast to int deletes the fractional part and moves toward zero. It is not Math.floor either — floor would give −10 for the second one. If you want rounding, say so with Math.round.

Where these go wrong#

  • Computing an average with integer division. (a + b) / 2 on two ints silently truncates. This is the single most common version of Trap 1.
  • Casting after the fact. (double) (sum / count) is too late. Cast an operand, not the result.
  • Using == on doubles. It compiles, it runs, and it returns false for values that look identical when printed.
  • Forgetting L and f. Both produce compile errors that name a type you did not write, which makes them confusing until you know that the literal has a type of its own.
  • Expecting overflow to announce itself. It does not. A total that suddenly goes negative is the symptom.
  • Reading 'A' + 1 as string concatenation. Both operands are numeric here, so it is addition. Concatenation needs a String on one side.

Test yourself in the free Kestrel Exams app

Topic-selectable practice — offline, no ads, no account.

Practice this topic →

Frequently asked questions#

Why does 7 / 2 give 3 in Java?

Because both operands are int, so Java performs integer division and discards the remainder. It is not rounding — 7 / 2 is 3, not 4. To get 3.5, at least one side must be a floating-point value: use 7.0 / 2, or cast one operand with (double) 7 / 2.

What are the eight primitive types in Java?

byte, short, int, long, float, double, char and boolean. Everything else in Java — including String — is a reference type, not a primitive.

Why can't I write long big = 10000000000;?

Because the literal itself is treated as an int before it is ever assigned, and it does not fit in an int. The compiler reports “integer number too large”. Add an L suffix: long big = 10000000000L.

Why is 0.1 + 0.2 not equal to 0.3?

double and float store values in binary, and 0.1 has no exact binary representation, just as one third has no exact decimal representation. The sum comes out as 0.30000000000000004. Never compare floating-point values with ==; compare the absolute difference against a small tolerance instead.

Is char a number or a letter?

Both. A char stores a number from 0 to 65535 that identifies a character. Because it is a number, arithmetic works on it: 'A' + 1 evaluates to the int 66. Casting back with (char) turns 66 into 'B'.

What is the difference between casting and rounding?

Casting a double to an int truncates toward zero — it deletes the fractional part rather than rounding it. (int) 9.99 is 9, and (int) -9.99 is -9, not -10. Use Math.round when you want rounding.