Skip to main content
Java beginner Lesson 3 of 58

Data Types in Java

Master Java's 8 primitive types, their wrapper classes, type casting, String handling, and how Java stores values in memory.

Java has two categories of types: primitives (8 built-in value types) and reference types (objects, including arrays, Strings, and every class you write). Understanding when to use each — and how Java stores them — is fundamental to writing correct Java code. Choosing the wrong type is a common source of subtle bugs, especially around overflow and floating-point precision.

The 8 Primitive Types

Primitives are the building blocks of all Java data. They are stored directly in memory (not as objects), which makes them fast and memory-efficient. Knowing each type’s size and range helps you pick the right one and avoid overflow surprises.

TypeSizeRangeDefaultUse for
byte8-bit-128 to 1270Raw binary data, file I/O
short16-bit-32,768 to 32,7670Rarely used directly
int32-bit-2.1B to 2.1B0Whole numbers (default integer type)
long64-bit±9.2 × 10¹⁸0LLarge whole numbers, timestamps
float32-bit~7 decimal digits0.0fRarely used; prefer double
double64-bit~15 decimal digits0.0Decimal numbers (default float type)
char16-bit’ ’ to ’￿'' ‘Single Unicode character
boolean1-bittrue / falsefalseFlags and conditions
// Integer types
byte  b  = 127;
short s  = 30_000;          // underscores allowed for readability
int   i  = 2_147_483_647;   // Integer.MAX_VALUE
long  l  = 9_223_372_036_854_775_807L; // L suffix required for long literals

// Floating-point
float  f = 3.14f;    // f suffix required
double d = 3.141592653589793; // default for decimal literals

// Character
char c = 'A';
char euro = '€'; // Unicode escape = €
System.out.println((int) c); // 65 — char is really an unsigned 16-bit int

// Boolean
boolean isJavaFun = true;
boolean isEmpty   = false;

Integer Overflow

Integer overflow is one of the most dangerous silent bugs in Java — the runtime does not throw an exception, it just wraps around to a wrong value. Knowing this upfront will save you from hard-to-trace calculation errors.

int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648 — overflows silently!

// Safe check using Math:
int result = Math.addExact(max, 1); // throws ArithmeticException on overflow

Floating-Point Precision

Floating-point types (double and float) use binary representation internally, which cannot exactly represent most decimal fractions. This is not a Java bug — it is how IEEE 754 works in every major language. The practical rule: never use double for money or any calculation where exact decimal values matter. Use BigDecimal instead.

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

// For exact decimal arithmetic, use BigDecimal:
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));          // 0.3 — exact

// Always construct BigDecimal from String, not double:
new BigDecimal(0.1)    // WRONG — 0.1000000000000000055511...
new BigDecimal("0.1")  // CORRECT — exactly 0.1

Wrapper Classes

Every primitive has a corresponding wrapper class — an object representation that adds utility methods and enables use in generics. You cannot put an int into a List<Integer>, but you can put an Integer. Java’s autoboxing feature handles the conversion automatically in most cases.

PrimitiveWrapperUseful methods
intIntegerparseInt, valueOf, MAX_VALUE, toBinaryString
longLongparseLong, toBinaryString, bitCount
doubleDoubleparseDouble, isNaN, isInfinite
booleanBooleanparseBoolean, toString
charCharacterisDigit, isLetter, toUpperCase
// Parsing strings to numbers — essential for user input
int age     = Integer.parseInt("25");
double price = Double.parseDouble("19.99");
boolean flag = Boolean.parseBoolean("true");

// Useful constants
System.out.println(Integer.MAX_VALUE);   // 2147483647
System.out.println(Integer.MIN_VALUE);   // -2147483648
System.out.println(Double.MAX_VALUE);    // 1.7976931348623157E308
System.out.println(Integer.toBinaryString(42)); // 101010

// Wrapper utility methods
System.out.println(Character.isDigit('7'));    // true
System.out.println(Character.isLetter('A'));   // true
System.out.println(Character.toUpperCase('a')); // A

Autoboxing and Unboxing

Java automatically converts between primitives and their wrapper classes. This convenience feature is transparent in most code, but the null-unboxing pitfall it creates is a common source of NullPointerException bugs.

// Autoboxing — primitive to wrapper
Integer boxed = 42;          // same as: Integer.valueOf(42)
List<Integer> list = new ArrayList<>();
list.add(99);                // int 99 autoboxed to Integer

// Unboxing — wrapper to primitive
int unboxed = boxed;         // same as: boxed.intValue()
int total = list.get(0) + 1; // unboxing before arithmetic

// Pitfall — null unboxing throws NullPointerException
Integer nullValue = null;
int x = nullValue; // NullPointerException at runtime!

Type Casting

Widening (automatic, no data loss)

Widening conversions are safe and automatic — Java allows them because the destination type is always large enough to hold the value. No data is lost.

byte → short → int → long → float → double
int i = 100;
long l = i;       // widening — automatic
double d = i;     // widening — automatic
System.out.println(d); // 100.0

Narrowing (explicit, may lose data)

Narrowing conversions require an explicit cast because you are telling the compiler “I know this might lose data — do it anyway.” Always check the range before narrowing to avoid silently corrupted values.

double d = 9.99;
int i = (int) d;   // explicit cast — truncates decimal
System.out.println(i); // 9 — NOT rounded, just truncated

long big = 1_000_000_000_000L;
int small = (int) big; // data loss — only keeps lower 32 bits
System.out.println(small); // -727379968 — garbage

// Safe check before narrowing:
if (big >= Integer.MIN_VALUE && big <= Integer.MAX_VALUE) {
    int safe = (int) big;
}

char ↔ int

char is an unsigned 16-bit integer under the hood, so it can be widened to int automatically and narrowed back with a cast. This lets you do arithmetic on characters — useful for encryption algorithms and character manipulation.

char c = 'A';
int code = c;          // widening: 65
char back = (char)(code + 1); // narrowing: 'B'
System.out.println(back); // B

Strings in Depth

String is immutable — once created, its value never changes. This is not just an implementation detail: it is what makes strings safe to share across threads, use as HashMap keys, and pass to methods without fear of the caller mutating them. Every “modification” creates a new object, so be aware of that cost when building strings in loops.

String s = "Hello";
s = s + " World"; // "Hello" is discarded; new String created in memory

// String pool — literals are interned (shared)
String a = "Java";
String b = "Java";
System.out.println(a == b);      // true — same object in pool
System.out.println(a.equals(b)); // true

// new String bypasses the pool
String c = new String("Java");
System.out.println(a == c);      // false — different objects
System.out.println(a.equals(c)); // true — same content

// ALWAYS use .equals() to compare String content, never ==

Common String Methods

String s = "  Hello, Java World!  ";

s.trim()                     // "Hello, Java World!" — removes leading/trailing whitespace
s.strip()                    // same but Unicode-aware (Java 11+)
s.toLowerCase()              // "  hello, java world!  "
s.toUpperCase()              // "  HELLO, JAVA WORLD!  "
s.contains("Java")           // true
s.startsWith("  Hello")      // true
s.endsWith("!")              // false (has trailing spaces)
s.replace("Java", "Python")  // "  Hello, Python World!  "
s.split(", ")                // ["  Hello", "Java World!  "]
s.indexOf("Java")            // 8
s.substring(8, 12)           // "Java"
s.charAt(8)                  // 'H' ... (index 8 of this string)
s.isEmpty()                  // false
s.isBlank()                  // false (Java 11+)
"".isEmpty()                 // true
"  ".isBlank()               // true

StringBuilder — Efficient String Building

When building strings in a loop, use StringBuilder to avoid creating many intermediate String objects. Each + concatenation in a loop allocates a new String, which becomes a garbage-collection burden. StringBuilder mutates a single internal buffer and produces one String at the end.

// BAD — creates 1000 String objects
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i; // new String every iteration
}

// GOOD — StringBuilder mutates in place
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString(); // one final String

// StringBuilder chaining
String csv = new StringBuilder()
    .append("Alice")
    .append(",")
    .append(25)
    .append(",")
    .append("Engineer")
    .toString(); // "Alice,25,Engineer"

var — Local Type Inference (Java 10+)

var reduces boilerplate in local variable declarations by letting the compiler infer the type from the initialiser. The type is still fixed at compile time — var is not dynamic typing, just a way to avoid writing the type name twice when it is already obvious from the right-hand side.

var count  = 0;               // int
var name   = "Alice";         // String
var items  = new ArrayList<String>(); // ArrayList<String>
var scores = new int[]{1, 2, 3};      // int[]

// var only works for local variables with an initialiser
// var x;           // ERROR — no initialiser
// var obj = null;  // ERROR — type cannot be inferred from null

Summary

CategoryTypesNotes
Integerbyte short int longDefault to int; use long for large values
Floating-pointfloat doubleDefault to double; use BigDecimal for exact math
Characterchar16-bit Unicode; rarely used directly
Booleanbooleantrue or false only
TextStringImmutable object; use StringBuilder for building
WrappersInteger Double etc.Object form of primitives; needed for generics

Frequently Asked Questions

What is the difference between int and Integer in Java?
int is a primitive — a raw value stored directly in memory, fast and efficient. Integer is a wrapper class — an object that wraps an int. You need Integer (or other wrappers) when working with generics (List<Integer>), nullability, or methods from the Collections API. Java auto-converts between them via autoboxing.
Why does 0.1 + 0.2 not equal 0.3 in Java?
Because double and float use IEEE 754 binary floating-point representation, which cannot exactly represent most decimal fractions. Use BigDecimal for financial or exact decimal arithmetic.
Is String a primitive in Java?
No. String is a class (java.lang.String). However, it has special treatment in Java — string literals are interned and Java provides the + operator for concatenation, which makes it feel built-in.