Back to Primitive Types & Wrappers
Q1Question 1 of 11

What are the eight primitive data types in Java?

Key Concepts
  • Eight primitives: byte (8-bit), short (16), int (32), long (64), float (32), double (64), char (16), boolean
  • Primitives hold raw values directly, not references to objects on the heap
  • Each has a fixed size defined by the Java spec, unlike C where sizes are platform-dependent
  • Default field values: 0 for integers, 0.0 for floats, '\u0000' for char, false for boolean
  • Never use float or double for money – use BigDecimal with String constructor
  • Type promotion: byte + byte evaluates as int, not byte – the JVM promotes small types

Definition

Java has eight primitives: byte, short, int, long, float, double, char, and boolean. Each is a fixed-size raw value, not an object.

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 – holds true or false.

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:

JAVA
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);
TEXT
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 int by default for whole numbers. Transaction counts, loop counters, array indices, quantities.
  • Use long when int is not big enough. Millisecond timestamps (System.currentTimeMillis()), database primary keys that exceed 2 billion, account sequence numbers.
  • Use double for scientific calculations where approximate values are acceptable. Ratios, percentages, statistical scores.
  • Never use float or double for money. Use BigDecimal with the String constructor.
  • Use boolean for yes/no flags. isActive, isVerified, hasOverdraft.
  • Use char for single characters. Account type codes ('S' for savings, 'C' for current).
  • Use byte for raw binary data. File bytes, network packets, image data.
  • Avoid short unless you have a specific memory constraint. It saves 2 bytes per value over int, which only matters in very large arrays.
JAVA
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:

JAVA
// 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.

JAVA
// 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:

JAVA
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.

JAVA
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:

JAVA
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.

JAVA
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, and char are promoted to int before any arithmetic.
  • If either operand is long, the other is promoted to long.
  • If either operand is float, the other is promoted to float.
  • If either operand is double, the other is promoted to double.

What exceptions can occur with primitives?

Integer division by zero throws ArithmeticException

JAVA
int a = 10;
int b = 0;
// int c = a / b;  // throws ArithmeticException: / by zero

Floating-point division by zero does NOT throw an exception

JAVA
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:

JAVA
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 Integer instead of int. But prefer primitives when the value is always present to avoid NullPointerException.
  • 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 write List. Project Valhalla (future Java) aims to allow value types in generics.
  • boolean size is JVM-dependent. The specification does not define the physical size of boolean. HotSpot typically uses 1 byte for a boolean field and 4 bytes for a boolean local (treated as int in the bytecode).
  • strictfp (before Java 17) controlled floating-point precision. Since Java 17, strict floating-point semantics are always used, so strictfp is no longer needed.
  • Unsigned operations (Java 8+). Java has no unsigned types, but Integer.toUnsignedLong(), Integer.compareUnsigned(), and Integer.divideUnsigned() provide unsigned behavior when needed.

Visual Overview

DIAGRAM
  ── 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.

---

Share
Use ← → arrow keys to navigate