BeginnerReading time: ~20 min

Syntax and Basic Types

Primitive types, wrapper classes, variables, scope, lifecycle, basic keywords and type casting

The building blocks of Java: primitive types, wrapper classes, variables, scope, lifecycle, and type casting.


1. Definition

What is it?

Java is a strongly typed language, meaning every variable must have its type declared (or inferred by the compiler via var). The language fundamentals are built on primitive types and their corresponding wrapper classes.

Why does it exist?

  • Type safety: the compiler catches type errors at compile-time
  • Performance: primitive types are stored directly on the stack, no heap allocation needed
  • Platform independence: type sizes are fixed across all platforms (e.g., int is always 32 bits)

Where does it fit?

This is the most fundamental layer of Java — everything else (OOP, Collections, Streams) builds on top of it.


2. Core Concepts

2.1 Primitive Types

Java has exactly 8 primitive types:

Type Size Default Range Usage
byte 8 bit 0 -128 to 127 Memory-efficient arrays, I/O
short 16 bit 0 -32,768 to 32,767 Rarely used
int 32 bit 0 -2ÂłÂč to 2ÂłÂč-1 Default integer type
long 64 bit 0L -2⁶³ to 2⁶³-1 Large numbers, timestamps
float 32 bit 0.0f ~±3.4×10³⁞ Rarely used, prefer double
double 64 bit 0.0d ~±1.7×10³⁰⁞ Default decimal type
char 16 bit '\u0000' 0 to 65,535 Unicode character
boolean ~1 bit* false true/false Logical value

*The boolean size is implementation-dependent: the JVM typically stores it as an int (32 bits) on the stack.

Key rules:

  • Primitives cannot be null
  • Primitives cannot be used in generics (List<int> ❌, List<Integer> ✅)
  • Integer literals default to int, decimal literals default to double

2.2 Wrapper Classes

Every primitive has a corresponding wrapper class:

Primitive Wrapper Cache Range
byte Byte -128 to 127 (full range)
short Short -128 to 127
int Integer -128 to 127
long Long -128 to 127
float Float no cache
double Double no cache
char Character 0 to 127
boolean Boolean TRUE / FALSE (both)

Autoboxing/Unboxing:

Integer x = 42;        // autoboxing: int → Integer
int y = x;             // unboxing: Integer → int
Integer z = null;
int w = z;             // ⚠ NullPointerException!

Integer Cache (-128 to 127):

Integer a = 127;
Integer b = 127;
System.out.println(a == b);    // true  (from cache)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);    // false (new objects!)
System.out.println(c.equals(d)); // true  (value comparison)

2.3 Variables, Scope, Lifecycle

Variable types:

Type Declared Default Lifetime
Local variable Inside method ❌ None (must initialize) End of block
Instance variable In class (non-static) ✅ Yes Until object is GC'd
Class variable static ✅ Yes Program termination
Parameter Method signature Provided by caller End of method

Scope rules:

public void example() {
    int x = 10;            // scope: entire method
    if (x > 5) {
        int y = 20;        // scope: this if block only
        System.out.println(x + y); // OK
    }
    // System.out.println(y); // ❌ Compile error! y not visible here
}

2.4 Type Casting

Implicit (widening) — automatic, lossless:

byte → short → int → long → float → double
char → int

Explicit (narrowing) — manual, may lose data:

double d = 9.78;
int i = (int) d;    // i = 9 (truncation, NOT rounding!)

int big = 130;
byte b = (byte) big; // b = -126 (overflow!)

Special rules:

  • Conversion between float and double may lose precision
  • int → float conversion can also lose precision (float has only 24-bit mantissa)

2.5 Literals, Defaults, and Compiler Inference

Literal syntax is part of everyday Java work, and it is also a common source of subtle bugs.

  • Integer literals like 42 are int by default
  • Long literals require the L suffix: 42L
  • Floating-point literals like 3.14 are double by default
  • Float literals require the f suffix: 3.14f
  • Underscores improve readability: 1_000_000
  • Binary, octal, and hexadecimal literals are supported: 0b1010, 077, 0xFF

The compiler also performs constant folding and range checks for compile-time constants. byte b = 10; compiles because the value fits into the target type, while byte b = 1000; does not.

Default values also matter during initialization:

  • instance fields get default values automatically
  • local variables never do
  • reference types default to null
  • numeric primitives default to 0 / 0.0
  • boolean defaults to false

2.6 Basic Java Keywords — a Quick Map

In early Java learning, many keywords keep appearing before their deeper meaning is obvious. This section gives you a mental map: not every detail is fully covered here, but you can already understand what role each keyword usually plays.

  • public — visible from anywhere; typically used for the outward API.
  • private — visible only inside the declaring class; used to protect internal state.
  • protected — visible in the package and to subclasses; common in inheritance APIs.
  • class — declares a class; defines an object type.
  • new — instantiates a new object; triggers heap allocation.
  • this — reference to the current object; useful for field access and constructor chaining.
  • static — class-level member, not tied to an instance; common for utilities and constants.
  • final — single assignment or no overriding/inheritance; common for constants and invariants.
  • abstract — incomplete implementation to be completed by a subtype.
  • extends — a class inherits from another class.
  • implements — a class fulfills an interface contract.
  • interface — behavioral contract used for abstraction and polymorphism.

Useful beginner rules:

  • Fields are most often private, while the outward-facing API is often public.
  • new creates the object, while the constructor initializes it.
  • Do not mix static and instance thinking: in a static context there is no this.
  • final does not always mean deep immutability; it may freeze the reference while the referenced object stays mutable.
public class User {
    private final String name;

    public User(String name) {
        this.name = name;
    }

    public static User guest() {
        return new User("guest");
    }
}

This short example already shows public, private, final, this, static, new, and class together. The later OOP chapters go deeper, but their basic role is already visible here.


3. Practical Usage

When to use what?

Situation Recommendation Why
General integers int Default, most common
Financial calculations BigDecimal ⚠ NOT double! Precision errors!
Numbers in collections Integer (wrapper) Generics requirement
Large dataset, memory matters primitive arrays Wrapper has 16+ bytes/element overhead
Boolean flag boolean primitive Simple, no null concerns
Nullable boolean (from DB) Boolean wrapper null = "we don't know"

When NOT to use?

  • ❌ double for financial calculations — 0.1 + 0.2 != 0.3
  • ❌ == for wrapper comparison — breaks outside cache range
  • ❌ float when not necessary — double is the default, float is rare

Practical Decision Checklist

When choosing a type in production code, ask these questions in order:

  1. Can the value be absent? If yes, a wrapper or dedicated domain type may be appropriate.
  2. Is arithmetic precision business-critical? If yes, prefer BigDecimal for decimal business values.
  3. Will this run in a tight loop or large dataset? If yes, avoid unnecessary boxing.
  4. Will this value cross an API boundary? Prefer explicit, intention-revealing types over “convenient” ones.

Examples:

  • int retryCount is usually better than Integer retryCount
  • Long orderId may be justified if persistence can represent an “unsaved” state as null
  • BigDecimal amount communicates domain precision better than double amount
  • boolean enabled is clearer than Boolean enabled unless three-state logic is truly required

4. Code Examples

Basic Example — Types and Conversion

public class TypeBasics {
    public static void main(String[] args) {
        // Primitives
        int count = 42;
        double price = 19.99;
        boolean active = true;
        char grade = 'A';

        // Widening (automatic)
        long bigCount = count;          // int → long, OK
        double bigPrice = count;        // int → double, OK

        // Narrowing (explicit cast required)
        int rounded = (int) price;      // 19 (truncation!)
        byte small = (byte) count;      // 42 (fits)

        // Wrapper autoboxing
        Integer boxed = count;          // autoboxing
        int unboxed = boxed;            // unboxing

        // var (Java 10+) — type inferred by compiler
        var name = "Alice";             // String
        var number = 42;                // int (NOT Integer!)
        var list = List.of(1, 2, 3);    // List<Integer>
    }
}

Advanced Example — Financial Calculation Pitfall

public class MoneyPitfall {
    public static void main(String[] args) {
        // ❌ WRONG: double precision error
        double price = 0.1;
        double quantity = 3;
        System.out.println(price * quantity);
        // Output: 0.30000000000000004 (!)

        // ✅ CORRECT: use BigDecimal
        BigDecimal bdPrice = new BigDecimal("0.1");
        BigDecimal bdQuantity = new BigDecimal("3");
        System.out.println(bdPrice.multiply(bdQuantity));
        // Output: 0.3
    }
}

Common Pitfall — Integer Cache

public class IntegerCacheTrap {
    public static void main(String[] args) {
        // Within cache (-128..127): WORKS
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b);      // true ✅

        // Outside cache: DOES NOT WORK
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);      // false ❌
        System.out.println(c.equals(d)); // true ✅

        // ⚠ Null unboxing
        Integer nullable = null;
        // int value = nullable;  // NullPointerException!
    }
}

5. Trade-offs

Aspect Primitive Wrapper
⚡ Performance Fast (stack, no allocation) Slower (heap allocation, GC)
đŸ’Ÿ Memory Minimal (e.g., int = 4 bytes) Significant (e.g., Integer = ~16 bytes)
🔧 Maintainability Simple but can't be null Requires null handling but more expressive
🔄 Flexibility Can't be used in generics Works with collections and generics

Concrete numbers:

  • int[1000] → ~4 KB memory
  • Integer[1000] → ~20 KB memory (5x more!)
  • One million Integer autoboxing operations → significant GC pressure

6. Common Mistakes

1. Using `==` with wrappers

// ❌ Wrong
if (value1 == value2) { ... }  // reference comparison!

// ✅ Correct
if (value1.equals(value2)) { ... }
// or
if (Objects.equals(value1, value2)) { ... }  // null-safe

2. Using `double` for financial calculations

// ❌ Never do this
double balance = 1000.00;
balance -= 999.99;
// balance = 0.010000000000047748 (NOT 0.01!)

// ✅ Use BigDecimal
BigDecimal balance = new BigDecimal("1000.00");
balance = balance.subtract(new BigDecimal("999.99"));
// balance = 0.01 ✅

3. Null unboxing

// ❌ NullPointerException thrown
Integer count = getCountFromDb(); // may return null
int total = count + 1;            // NPE!

// ✅ Null check
int total = (count != null) ? count + 1 : 1;
// or use Optional
int total = Optional.ofNullable(count).orElse(0) + 1;

4. Narrowing cast overflow

// ❌ Silently produces wrong result
int big = 128;
byte b = (byte) big;  // b = -128 (overflow, no exception!)

// ✅ Check before casting
if (big >= Byte.MIN_VALUE && big <= Byte.MAX_VALUE) {
    byte b = (byte) big;
} else {
    throw new ArithmeticException("Value out of byte range");
}
// or Java 8+:
byte b = Math.toIntExact(big); // ArithmeticException on overflow

7. Deep Dive

When does primitive vs wrapper choice matter?

  • Hot path (tight loops, real-time processing): always use primitives. Autoboxing in a million-iteration loop causes measurable slowdown.
  • Domain models: use wrapper if the value is nullable (e.g., Integer age — "we don't know the age" vs int age — "age is 0").
  • API design: Optional<Integer> is better than a nullable Integer.

Integer Cache Customization

The JVM's -XX:AutoBoxCacheMax=<size> flag can extend the Integer cache range. In high-frequency systems (e.g., IDs ranging 1–10,000), this can improve performance.

`var` usage — when yes, when no?

// ✅ Good: right-hand side makes type obvious
var users = new ArrayList<User>();
var response = httpClient.send(request, BodyHandlers.ofString());

// ❌ Bad: type is not obvious
var result = process();          // what type is this?
var x = flag ? getA() : getB(); // which type?

JIT Optimization

The JIT compiler can often eliminate autoboxing via escape analysis when the wrapper object doesn't "escape" the method. But don't rely on this — always measure in production code!

Primitive Streams vs Boxing Overhead

This topic becomes relevant again in the Stream API chapter. Stream<Integer> looks convenient, but repeated boxing and unboxing can become expensive in aggregation-heavy code.

int sum = IntStream.range(0, 1_000_000)
    .filter(value -> value % 2 == 0)
    .sum();

If the data is truly numeric, IntStream, LongStream, and DoubleStream are often better fits than object streams.

API Boundaries and Null Semantics

Choosing between int and Integer is not only about performance. It also defines business meaning.

  • int quantity means the value must always exist
  • Integer quantity means absence is representable
  • OptionalInt can express optionality without boxing in internal APIs

That semantic signal often matters more than the raw memory cost.

Floating Point Comparison

// ❌ Never use == with double
double a = 0.1 + 0.2;
if (a == 0.3) { ... }  // false!

// ✅ Epsilon-based comparison
private static final double EPSILON = 1e-10;
if (Math.abs(a - 0.3) < EPSILON) { ... }  // true

8. Interview Questions

Why can `Integer` cause a `NullPointerException` even if you never call a method on it?

Because unboxing is an implicit method call inserted by the compiler. If Integer value = null; and later int x = value;, Java effectively performs value.intValue(), which throws NullPointerException.

When would you prefer a wrapper over a primitive in a domain model?

Use a wrapper when missingness is meaningful. For example, Integer age can represent “unknown”, while int age cannot. The choice should be driven by domain semantics first, not habit.

Why does `short s = 1; s += 1;` compile, but `s = s + 1;` does not?

+= performs an implicit cast after evaluation. s + 1 is promoted to int, so plain assignment back to short would be narrowing and requires an explicit cast.

Because double stores values in binary floating-point format, so many decimal fractions cannot be represented exactly. BigDecimal stores decimal values with controlled precision and rounding, which is essential for business calculations.


9. Glossary

Term Definition
Autoboxing Automatic conversion of a primitive type to its wrapper class (int → Integer)
Unboxing Automatic conversion of a wrapper class to its primitive (Integer → int)
Widening Implicit type conversion from a smaller type to a larger one (lossless)
Narrowing Explicit type conversion from a larger type to a smaller one (may lose data)
Integer Cache The JVM caches Integer objects for values -128 to 127 via Integer.valueOf()
Stack Memory area where local primitive variables and references are stored
public Access level visible from anywhere
private Access level visible only inside the declaring class
class Java keyword used to declare a class
new Keyword that initiates creation of a new object
String Pool → See: String Handling
Escape Analysis JIT technique: determines if an object "escapes" its method scope

10. Cheatsheet

  • 🔱 8 primitive types: byte, short, int, long, float, double, char, boolean
  • 📩 Wrapper = primitive + null + generics compatibility (but more expensive)
  • ⚠ == on wrappers compares references, use .equals()!
  • 💰 For money, ALWAYS use BigDecimal, never double
  • 🎯 Integer cache: -128 to 127 (extendable via JVM flag)
  • 📏 Widening is automatic, narrowing requires explicit cast
  • đŸš« Null unboxing = NPE — always check for null!
  • 🔍 var (Java 10+) — only when type is obvious from context
  • 🔐 public / private / protected — define where a member is visible from
  • đŸ—ïž class + new — one declares a type, the other creates an instance
  • ⚡ Hot path = primitive, domain model = wrapper if nullable needed
  • 🧼 float ≈ 7 decimal digits, double ≈ 15 decimal digits precision

🎼 Games

13 questions