Definition
Java's eight primitives fall into four groups:
- Integers:
byte(8 bits),short(16 bits),int(32 bits),long(64 bits) – all signed. - Floating-point:
float(32 bits),double(64 bits) – IEEE 754 standard. - Character:
char(16 bits) – unsigned, holds one Unicode character. - Boolean:
boolean– holdstrueorfalse.
Unlike objects, primitives store the value directly in the variable, not a reference to something on the heap. Their sizes are fixed by the Java Language Specification and identical on every platform.
Think of a bank form with eight types of boxes. The integer boxes come in four sizes: a tiny box for a status code (byte), a small box for a branch ID (short), a standard box for a transaction count (int), and a large box for an account sequence number (long). The decimal boxes come in two sizes for ratios and scores (float, double) but are never used for rupees. There is a single-letter box for the account type (char) and a yes/no checkbox for active status (boolean). Each box has a fixed size printed on the form, and if you leave a box blank, it auto-fills with zero or false.
Understanding the Concepts
What are the eight types and their ranges?
Here is the complete reference table:
| Type | Size | Min value | Max value | Default | Use case |
|---|---|---|---|---|---|
| byte | 8 bits | -128 | 127 | 0 | Raw binary data, status codes |
| short | 16 bits | -32,768 | 32,767 | 0 | Rarely used directly |
| int | 32 bits | -2,147,483,648 | 2,147,483,647 | 0 | Default for counts, IDs |
| long | 64 bits | -9.2 x 10^18 | 9.2 x 10^18 | 0L | Timestamps, large sequences |
| float | 32 bits | ~1.4 x 10^-45 | ~3.4 x 10^38 | 0.0f | Scientific, never money |
| double | 64 bits | ~4.9 x 10^-324 | ~1.8 x 10^308 | 0.0 | Default for decimals, never money |
| char | 16 bits | 0 (\u0000) | 65,535 (\uFFFF) | '\u0000' | Single character |
| boolean | ~1 bit | false | true | false | Flags, conditions |
You can verify these ranges in code using the wrapper class constants:
System.out.println("int range: " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
System.out.println("long range: " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);
System.out.println("char range: " + (int) Character.MIN_VALUE + " to " + (int) Character.MAX_VALUE);
int range: -2147483648 to 2147483647
long range: -9223372036854775808 to 9223372036854775807
char range: 0 to 65535
How do you choose which type to use?
The rules are simple:
- Use
intby default for whole numbers. Transaction counts, loop counters, array indices, quantities. - Use
longwhenintis not big enough. Millisecond timestamps (System.currentTimeMillis()), database primary keys that exceed 2 billion, account sequence numbers. - Use
doublefor scientific calculations where approximate values are acceptable. Ratios, percentages, statistical scores. - Never use
floatordoublefor money. UseBigDecimalwith theStringconstructor. - Use
booleanfor yes/no flags.isActive,isVerified,hasOverdraft. - Use
charfor single characters. Account type codes ('S'for savings,'C'for current). - Use
bytefor raw binary data. File bytes, network packets, image data. - Avoid
shortunless you have a specific memory constraint. It saves 2 bytes per value overint, which only matters in very large arrays.
public class Account {
private long accountId; // long: may exceed int range
private int transactionCount; // int: default for counts
private boolean active; // boolean: yes/no
private char type; // char: 'S' or 'C'
private BigDecimal balance; // BigDecimal: never float/double for money
}
How do you write literals for each type?
Each type has its own literal syntax:
// Integer literals
byte b = 127; // no suffix, must fit in byte range
short s = 32000; // no suffix, must fit in short range
int i = 1_000_000; // underscore for readability (Java 7+)
long l = 9_876_543_210L; // L suffix required for long
// Floating-point literals
float f = 3.14f; // f suffix required for float
double d = 3.14159; // no suffix, double is the default
double sci = 1.5e6; // scientific notation: 1,500,000.0
// Character literal
char c = 'A'; // single quotes
char unicode = '\u0041'; // Unicode escape: also 'A'
char rupee = '\u20B9'; // ₹ symbol
// Boolean literal
boolean flag = true; // only true or false
// Base prefixes
int hex = 0xFF; // hexadecimal: 255
int octal = 077; // octal: 63
int binary = 0b1010; // binary (Java 7+): 10
The underscore _ in numeric literals (like 1_000_000) is ignored by the compiler. It is purely for readability, and it is especially useful for large amounts in banking code.
Why should you never use float or double for money?
This is one of the most important rules in banking software. IEEE 754 floating-point numbers cannot represent many decimal fractions exactly.
// The problem: floating-point arithmetic is approximate
double a = 0.1;
double b = 0.2;
System.out.println("0.1 + 0.2 = " + (a + b));
// Output: 0.1 + 0.2 = 0.30000000000000004
// After 10,000 additions, the error compounds
double total = 0;
for (int i = 0; i < 10_000; i++) {
total += 0.01;
}
System.out.println("10000 x 0.01 = " + total);
// Output: 10000 x 0.01 = 99.99999999999857 (not 100.00)
The fix is to use BigDecimal with the String constructor:
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println("0.1 + 0.2 = " + a.add(b));
// Output: 0.1 + 0.2 = 0.3 (exact)
// WRONG: new BigDecimal(0.1) still captures the double's imprecision
BigDecimal wrong = new BigDecimal(0.1);
System.out.println(wrong);
// Output: 0.1000000000000000055511151231257827021181583404541015625
Always use new BigDecimal("0.1") with a String, never new BigDecimal(0.1) with a double.
What happens when a value overflows?
Java does not throw an exception on integer overflow. The value wraps around silently.
int maxInt = Integer.MAX_VALUE; // 2,147,483,647
int overflowed = maxInt + 1; // -2,147,483,648 (wraps to MIN_VALUE)
System.out.println("MAX_VALUE + 1 = " + overflowed);
long maxLong = Long.MAX_VALUE;
long overflowedLong = maxLong + 1; // -9,223,372,036,854,775,808
System.out.println("MAX_LONG + 1 = " + overflowedLong);
This is dangerous in banking. If a counter or sequence wraps around, you could get duplicate IDs or negative counts. To catch overflow safely, use Math.addExact(), which throws ArithmeticException on overflow:
try {
int safe = Math.addExact(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
System.out.println("Overflow detected: " + e.getMessage());
}
// Output: Overflow detected: integer overflow
What is type promotion?
When you perform arithmetic on small types (byte, short, char), the JVM promotes them to int before doing the calculation. This surprises many developers.
byte a = 50;
byte b = 60;
// byte c = a + b; // COMPILE ERROR: a + b evaluates to int, not byte
int c = a + b; // correct: result is int
byte d = (byte)(a + b); // explicit cast back to byte (may lose data)
System.out.println(c); // 110 (as int)
System.out.println(d); // 110 (fits in byte, so no data loss)
// But if the result exceeds byte range:
byte x = 100;
byte y = 100;
byte z = (byte)(x + y); // 200 overflows byte → wraps to -56
System.out.println(z); // -56
The promotion rules are:
byte,short, andcharare promoted tointbefore any arithmetic.- If either operand is
long, the other is promoted tolong. - If either operand is
float, the other is promoted tofloat. - If either operand is
double, the other is promoted todouble.
What exceptions can occur with primitives?
Integer division by zero throws ArithmeticException
int a = 10;
int b = 0;
// int c = a / b; // throws ArithmeticException: / by zero
Floating-point division by zero does NOT throw an exception
double a = 10.0;
double b = 0.0;
double c = a / b; // Infinity (not an exception)
double d = 0.0 / 0.0; // NaN (Not a Number)
System.out.println(c); // Infinity
System.out.println(d); // NaN
System.out.println(Double.isInfinite(c)); // true
System.out.println(Double.isNaN(d)); // true
This difference catches people off guard. Integer division by zero crashes. Floating-point division by zero silently produces Infinity or NaN.
How is this different from C/C++ primitives?
In C, the size of int is platform-dependent. It might be 16 bits on one system and 32 bits on another. In Java, int is always 32 bits on every platform. This is part of Java's platform independence.
| Feature | Java | C/C++ |
|---|---|---|
| int size | Always 32 bits | Platform-dependent (usually 32) |
| long size | Always 64 bits | Platform-dependent (32 or 64) |
| Overflow behavior | Wraps silently | Undefined behavior for signed |
| boolean type | Built-in | No built-in (uses int 0/1) |
| char size | 16 bits (Unicode) | 8 bits (ASCII) |
| Default values | Defined for fields | No defaults (garbage) |
What about var with primitives?
var infers the type from the literal:
var a = 10; // infers int (default for integer literals)
var b = 10L; // infers long (L suffix)
var c = 3.14; // infers double (default for decimal literals)
var d = 3.14f; // infers float (f suffix)
var e = 'A'; // infers char
var f = true; // infers boolean
// If you want a byte or short with var, you must cast:
var g = (byte) 10; // infers byte
var h = (short) 100; // infers short
Without the suffix or cast, var x = 10 is always int and var x = 3.14 is always double.
What should a senior engineer know?
- Primitives cannot be null. If you need a nullable number (e.g., a database column that allows NULL), use the wrapper class
Integerinstead ofint. But prefer primitives when the value is always present to avoidNullPointerException. - Each primitive has a wrapper class:
Byte,Short,Integer,Long,Float,Double,Character,Boolean. Autoboxing converts between them automatically, but it has performance implications (covered in Q30). - Primitives cannot be used as generic type arguments. You cannot write
List. You must writeList. Project Valhalla (future Java) aims to allow value types in generics. booleansize is JVM-dependent. The specification does not define the physical size ofboolean. HotSpot typically uses 1 byte for abooleanfield and 4 bytes for abooleanlocal (treated as int in the bytecode).strictfp(before Java 17) controlled floating-point precision. Since Java 17, strict floating-point semantics are always used, sostrictfpis no longer needed.- Unsigned operations (Java 8+). Java has no unsigned types, but
Integer.toUnsignedLong(),Integer.compareUnsigned(), andInteger.divideUnsigned()provide unsigned behavior when needed.
Visual Overview
── The eight primitive types ──
Integers (signed):
byte ████ 8 bits -128 to 127
short ████████ 16 bits -32K to 32K
int ████████████████ 32 bits -2.1B to 2.1B
long ████████████████████████████████ 64 bits -9.2E18 to 9.2E18
Floating-point (IEEE 754):
float ████████████████ 32 bits ~7 decimal digits
double ████████████████████████████████ 64 bits ~15 decimal digits
Other:
char ████████ 16 bits 0 to 65535 (Unicode)
boolean ██ ~1 bit true or false
── Type promotion chain ──
byte → short → int → long → float → double
↑
char
Any arithmetic on byte/short/char promotes to int first.
If either operand is long/float/double, the other promotes to match.
── Money rule ──
float/double → 0.1 + 0.2 = 0.30000000000000004 (WRONG for money)
BigDecimal → 0.1 + 0.2 = 0.3 (CORRECT for money)
Key Takeaway
Java has exactly eight primitive data types, and knowing them thoroughly is foundational. The four integer types are byte, short, int, and long, with sizes of 8, 16, 32, and 64 bits. The two floating-point types are float and double, following the IEEE 754 standard. The char type holds a single 16-bit Unicode character. And the boolean type holds true or false.
In banking code, I use int as the default for whole numbers like transaction counts, long for values that exceed two billion like timestamps and database sequences, boolean for flags like isActive, and char for single-character codes. I never use float or double for money because IEEE 754 cannot represent decimal fractions exactly. Adding 0.1 and 0.2 as doubles gives 0.30000000000000004, not 0.3. For every rupee amount, I use BigDecimal constructed from a String.
Two things catch developers off guard. First, integer overflow wraps around silently. Integer.MAX_VALUE plus one becomes Integer.MIN_VALUE with no exception. Use Math.addExact() in banking code to catch this. Second, arithmetic on byte, short, or char promotes the operands to int before the calculation, so byte plus byte gives an int, not a byte. You need an explicit cast to store the result back in a byte, and that cast can silently lose data if the result exceeds the byte range.
The sizes are fixed by the Java specification and identical on every platform, unlike C where int might be 16 or 32 bits depending on the system. This is part of what makes Java portable. And every primitive field gets a default value if not assigned: zero for numbers, false for boolean, and the null character for char. Local variables, as we saw earlier, get no default and must be assigned before use.
One sentence to remember: the eight primitives are byte, short, int, long, float, double, char, and boolean, each a fixed-size raw value with a guaranteed default, and money always uses BigDecimal, never float or double.
---