# File 1: Java Fundamentals

Java is often introduced as "write once, run anywhere," but that slogan only makes sense after you understand what Java is trying to optimize for: predictable behavior, strong tooling, portable runtime execution, and maintainability in medium-to-large systems. If you have seen Python or JavaScript first, Java can feel more explicit and more verbose. That is intentional. Java asks you to declare structure early so the compiler, IDE, runtime, and teammates can reason about your code reliably.

This file builds the base mental model you need before object-oriented design or concurrency starts to make sense. The goal is not just to memorize syntax. The goal is to understand what happens from source code to running program, why types matter, how control flow shapes logic, and how Java code is organized in real projects.

## Why Java Still Matters

Java has been around for decades, but it stays relevant because it solves real engineering problems well:

- teams need code that is easy to refactor safely
- backend systems need stable performance under load
- large organizations need strong tooling, dependency management, and observability support
- the JVM ecosystem provides mature libraries for networking, concurrency, persistence, messaging, and security

In production, Java commonly appears in:

- backend APIs handling payments, inventory, identity, notifications, and reporting
- internal enterprise systems with long maintenance lifetimes
- streaming and data-processing systems
- Android history, although modern Android development now leans heavily on Kotlin
- high-throughput services built with frameworks such as Spring Boot, Micronaut, Quarkus, or Dropwizard

Java is not popular because it is the shortest language to write. It is popular because it creates a strong balance between developer productivity, runtime performance, and operational predictability.

## The Java Ecosystem: JDK, JRE, and JVM

One of the first sources of confusion for beginners is that people casually say "install Java" when they actually mean different pieces of the platform.

### Intuition

Think of Java like a small software factory:

- you write source code
- a compiler turns it into bytecode
- a runtime executes that bytecode
- development tools help you debug, package, test, and ship the application

The three terms below refer to different layers in that process.

### JDK

The JDK, or Java Development Kit, is what you install when you want to build Java programs. It includes:

- the compiler `javac`
- the Java launcher `java`
- debugging and inspection tools like `jdb`, `jstack`, `jmap`, `jcmd`
- the runtime needed to execute programs
- standard libraries used by your code

If you are writing, compiling, packaging, or debugging Java, the JDK is your full toolkit.

### JRE

The JRE, or Java Runtime Environment, historically referred to the runtime needed to execute already-compiled Java applications. In older explanations, the distinction was:

- JDK = build and run
- JRE = run only

In modern Java distributions, the JRE is less emphasized as a separate installable concept. Still, the term matters because many articles and interviews use it. Conceptually, it means the runtime layer rather than the full developer toolkit.

### JVM

The JVM, or Java Virtual Machine, is the engine that actually runs Java bytecode. It handles:

- loading classes into memory
- verifying bytecode safety
- interpreting or JIT-compiling code into machine instructions
- memory allocation and garbage collection
- thread scheduling integration with the operating system

The JVM is the main reason Java is portable. Your source code is compiled into bytecode, and that bytecode can run on any machine with a compatible JVM.

### Runtime Architecture Diagram

```mermaid
flowchart LR
    A[.java source files] --> B[javac compiler]
    B --> C[.class bytecode]
    C --> D[Class Loader]
    D --> E[JVM Runtime]
    E --> F[Interpreter]
    E --> G[JIT Compiler]
    G --> H[Native Machine Code]
    E --> I[Garbage Collector]
    E --> J[Heap and Thread Stacks]
```

### How It Works Internally

When you run a Java program, the JVM does not immediately convert every method into optimized native machine code. That would make startup too expensive. Instead, it usually starts by interpreting bytecode and watching which methods are "hot," meaning frequently executed. Hot code paths are then compiled by the Just-In-Time compiler into optimized native instructions.

That means Java has two important performance characteristics:

- startup can be slower than a small native binary because the runtime is initializing and warming up
- long-running applications can become very fast because the JVM gathers execution data and optimizes real usage patterns

This is why Java is a strong fit for backend services that run continuously for hours or days.

### Common Misconceptions

- "Java is purely interpreted." Not true. Java is compiled to bytecode, then interpreted and JIT-compiled at runtime.
- "Java is slow because it runs in a virtual machine." That is outdated thinking. For many long-running server workloads, modern JVM performance is excellent.
- "Installing Java means installing only the JVM." In practice, developers usually install a full JDK.

## A Minimal Java Program

```java
public class HelloApplication {
    public static void main(String[] args) {
        System.out.println("Hello, Java");
    }
}
```

### What Each Part Means

- `public class HelloApplication`: defines a class named `HelloApplication`
- `public static void main(String[] args)`: the standard entry point for a standalone Java application
- `String[] args`: command-line arguments passed to the program
- `System.out.println(...)`: writes text to standard output

### Why Java Starts Here

Java treats execution as behavior that belongs inside a type. That is why even a simple program lives inside a class. In more advanced Java, you will also see records, enums, interfaces, and frameworks that manage object creation for you, but this class-based entry point is still the core model.

### Production Relevance

Real services often start with more than one line in `main`, but the high-level idea is similar:

- bootstrap configuration
- create or start the application container
- connect logging and monitoring
- register shutdown hooks
- start serving traffic

Spring Boot, for example, still starts with a `main` method, even though most of the heavy lifting is hidden behind the framework.

## Variables and Data Types

Types are one of Java's biggest strengths. A type tells both the compiler and the reader what kind of data a variable can hold and what operations are valid on that data.

### Intuition

You can think of types as contracts for data. They prevent a large class of bugs early. If a method expects an `int`, you cannot silently pass a string. If a variable holds a `Customer`, the IDE can show what methods and fields are available.

In fast-growing codebases, this matters a lot. The compiler becomes a guardrail against accidental misuse.

### Primitive Types

Primitive types store simple values directly.

| Type | Typical Use | Example |
| --- | --- | --- |
| `byte` | raw binary data, very small integers | `byte flags = 1;` |
| `short` | niche memory-sensitive numeric storage | `short yearOffset = 12;` |
| `int` | default integer arithmetic | `int itemCount = 42;` |
| `long` | large counters, timestamps, IDs | `long epochMillis = System.currentTimeMillis();` |
| `float` | specialized numeric work | `float ratio = 0.25f;` |
| `double` | default floating-point math | `double price = 19.99;` |
| `char` | individual UTF-16 code unit | `char grade = 'A';` |
| `boolean` | true/false logic | `boolean active = true;` |

### Reference Types

Reference types store a reference to an object, not the object value inline. Examples include:

- `String`
- arrays like `int[]`
- user-defined classes like `Order`
- interfaces like `List<String>`
- wrapper types like `Integer`

```java
int quantity = 5;
String status = "SHIPPED";
Order order = new Order();
```

Here:

- `quantity` directly holds a primitive value
- `status` holds a reference to a `String` object
- `order` holds a reference to an `Order` object

### Stack and Heap Mental Model

```mermaid
flowchart TD
    A[Method invocation] --> B[Stack frame created]
    B --> C[Local primitives stored directly]
    B --> D[Local references stored]
    D --> E[Objects allocated on heap]
    E --> F[Shared until unreachable]
    F --> G[Garbage collector reclaims memory]
```

This diagram is simplified, but it is good enough for a beginner mental model:

- local method state lives in a stack frame
- objects usually live on the heap
- references point to heap objects
- when objects are no longer reachable, garbage collection can reclaim them

### Why This Matters in Practice

If you accidentally keep references to objects longer than needed, memory usage grows. That is how some memory leaks happen in Java: not by forgetting `free()` like in C, but by retaining references in caches, static fields, thread locals, listeners, or long-lived collections.

### `var` and Type Inference

Modern Java supports local variable type inference with `var`.

```java
var customerName = "Priya";
var orderCount = 12;
```

This does not make Java dynamically typed. The compiler still infers a concrete static type.

Use `var` when the type is obvious from the right-hand side. Avoid it when it hides important meaning.

Bad:

```java
var result = service.execute(config, payload, strategy);
```

Better:

```java
PaymentResponse result = service.execute(config, payload, strategy);
```

### Common Pitfalls

- confusing primitives with wrapper types like `int` versus `Integer`
- assuming `null` is valid for primitives; it is not
- using floating-point types for money; production systems usually prefer `BigDecimal`
- overusing `var` until the code becomes harder to read

## Operators and Expressions

Operators are how you transform and compare values. Beginners often see them as simple symbols, but production bugs frequently come from operator misuse, especially around comparison, short-circuiting, and numeric behavior.

### Arithmetic Operators

Java supports familiar arithmetic operations:

```java
int total = 10 + 5;
int remaining = 10 - 3;
int doubled = 4 * 2;
int quotient = 9 / 2;
int remainder = 9 % 2;
```

Notice that `9 / 2` with integers becomes `4`, not `4.5`. This matters in billing, pagination, and rate calculations.

### Comparison Operators

```java
int threshold = 100;
boolean high = threshold > 80;
boolean exact = threshold == 100;
boolean different = threshold != 50;
```

For primitives, `==` compares values. For objects, `==` compares whether two references point to the same object.

That distinction is critical.

```java
String a = new String("ok");
String b = new String("ok");

System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true
```

In domain code, use `equals()` for logical comparison unless you explicitly care about identity.

### Logical Operators

```java
boolean hasToken = true;
boolean notExpired = true;

boolean allowed = hasToken && notExpired;
boolean fallback = hasToken || notExpired;
boolean blocked = !hasToken;
```

`&&` and `||` short-circuit. That means Java may skip evaluating the right side if the left side already determines the result.

This is useful and common:

```java
if (user != null && user.isActive()) {
    // safe because the second check only runs when user is not null
}
```

### Assignment and Increment Operators

```java
int attempts = 0;
attempts += 1;
attempts++;
```

Be careful with pre-increment and post-increment in larger expressions. They are legal, but often make code harder to reason about. Clear code is usually better than clever code.

### Ternary Operator

```java
String label = isAdmin ? "admin" : "user";
```

This is useful for simple conditional value selection. If the expression becomes nested or long, switch to an `if` block for readability.

### Pitfalls

- integer division truncates toward zero
- `==` on objects compares identity, not logical equality
- combining too many operators in one expression makes debugging harder
- side effects inside expressions reduce clarity

## Control Flow: If, Loops, and Switch

Control flow determines how your program makes decisions and repeats work. In backend systems, this often appears in validation logic, retry loops, data transformation, request routing, and state handling.

## Conditional Logic with `if`

```java
if (amount <= 0) {
    throw new IllegalArgumentException("Amount must be positive");
} else if (amount > 10_000) {
    System.out.println("Manual review required");
} else {
    System.out.println("Payment accepted");
}
```

### Why `if` Matters

In production code, many failures happen because conditions are incomplete, ordered incorrectly, or too hard to understand. Good conditionals are:

- explicit
- mutually understandable
- narrow in purpose

For example, authentication code often checks conditions in a deliberate order:

1. is the token present?
2. is the token well-formed?
3. is it expired?
4. does it map to a valid user?

That order affects both correctness and security.

## Loops

### `for` loop

```java
for (int index = 0; index < orders.size(); index++) {
    System.out.println(orders.get(index));
}
```

Useful when you need the index or precise control.

### Enhanced `for` loop

```java
for (String email : emails) {
    System.out.println(email);
}
```

Useful when you only need each element.

### `while` loop

```java
while (!queue.isEmpty()) {
    process(queue.poll());
}
```

Useful when the stopping condition is not naturally tied to an index.

### Real-World Use Cases

- polling a message queue until empty in a batch job
- retrying an external API call with a maximum attempt count
- iterating through rows returned from a database or file
- walking through a list of events to build an aggregate state

### Pitfalls

- off-by-one errors in indexed loops
- modifying a collection incorrectly while iterating
- infinite loops caused by state that never changes
- putting expensive work inside nested loops without noticing the performance cost

If a loop processes millions of records, simple mistakes become production incidents.

## `switch`

Java's `switch` is useful when one variable determines multiple branches.

```java
String region = "EU";

switch (region) {
    case "US":
        System.out.println("Use US tax rules");
        break;
    case "EU":
        System.out.println("Use EU VAT rules");
        break;
    default:
        System.out.println("Use global defaults");
}
```

Modern Java also supports switch expressions, which are often cleaner.

```java
String action = switch (region) {
    case "US" -> "usd-pricing";
    case "EU" -> "eur-pricing";
    default -> "default-pricing";
};
```

### Why This Matters in Production

`switch` logic often appears in:

- request routing based on event type
- status-to-action mapping
- feature behavior by region or tenant type
- serialization and parsing logic

The main design risk is letting a `switch` grow so large that it becomes a code smell. At some point, polymorphism or strategy objects become a better fit.

## Methods and Basic Program Structure

Methods are where Java code becomes reusable, testable, and readable.

### Intuition

A method should represent one coherent action. When methods are too large, names stop helping and reasoning becomes difficult. Good Java code often feels readable because each method does one thing at the right level of abstraction.

### Example

```java
public class InvoiceService {

    public double calculateTotal(double subtotal, double taxRate) {
        validateSubtotal(subtotal);
        return subtotal + (subtotal * taxRate);
    }

    private void validateSubtotal(double subtotal) {
        if (subtotal < 0) {
            throw new IllegalArgumentException("Subtotal cannot be negative");
        }
    }
}
```

### Why This Structure Is Better

Instead of mixing validation, math, logging, and persistence in one method, this design separates concerns. That gives you:

- clearer intent
- easier unit testing
- simpler debugging
- less duplication when validation rules are reused

### Method Signature Elements

- access modifier: `public`, `private`, and so on
- return type: `double`, `String`, custom types, or `void`
- method name: should describe behavior
- parameters: inputs required by the method
- thrown exceptions: sometimes declared explicitly

### Parameter Passing in Java

Java is always pass-by-value.

For primitives, the value itself is copied.

For objects, the reference is copied. That copied reference still points to the same underlying object.

```java
class Counter {
    int value;
}

void increment(Counter counter) {
    counter.value++;
}
```

If you call `increment(counter)`, the original object changes because both the caller and callee reference the same object. But if the method assigns `counter = new Counter();`, the caller does not start pointing to that new object.

This is a very common interview topic and a very common source of beginner confusion.

### Common Method Design Mistakes

- methods that do too many things
- boolean flags like `process(true, false, true)` that make call sites unreadable
- hidden side effects such as mutating shared state unexpectedly
- returning `null` casually when an empty collection, exception, or `Optional` would be clearer

## Packages, Imports, and Source Organization

As Java codebases grow, organization matters as much as syntax.

### Packages

Packages group related classes.

```java
package com.example.orders;
```

In real systems, packages usually reflect bounded areas of responsibility, such as:

- `com.company.auth`
- `com.company.billing`
- `com.company.notifications`

### Imports

Imports let you refer to classes without writing their fully qualified names everywhere.

```java
import java.time.Instant;
import java.util.List;
```

### Real-World Convention

In production services, code is usually organized around features or layers. A simple service might contain packages for:

- controllers or API endpoints
- services or business logic
- repositories or persistence
- domain models
- configuration

That structure becomes much easier to reason about once you already understand classes, methods, and object interactions.

## Build Tools and the Everyday Java Workflow

You can compile Java with `javac` directly, but professional projects almost always use a build tool.

### Maven and Gradle

These tools handle:

- dependency downloads
- compilation
- test execution
- packaging into JARs
- plugin-based workflows like code generation or static analysis

### Why This Matters

In a real backend service, you rarely work with one file in isolation. Your build tool defines how the application is assembled, which library versions are used, and what happens in CI before code is deployed.

If Java syntax is the grammar, Maven or Gradle is part of the operating system of day-to-day Java development.

## How Fundamentals Show Up in Production Systems

The concepts in this file are not classroom-only topics.

- Strong typing makes API contracts and refactors safer.
- JVM portability allows the same service artifact to run in local development, test, and cloud environments.
- Control flow drives validation, retries, workflows, and business rules.
- Method design strongly affects maintainability and testability.
- Correct type choice matters for money, timestamps, concurrency, and memory usage.

For example, an order-processing service may:

1. start inside a JVM launched by a container image
2. receive a request into a controller method
3. validate fields with `if` conditions
4. loop through order items
5. call helper methods to calculate totals and taxes
6. store domain objects on the heap while processing the request

That is not advanced Java. That is Java fundamentals in production.

## Key Takeaways

- Java's real strength is not brevity. It is explicit structure, strong tooling, and predictable runtime behavior.
- The JDK is your development toolkit, the JVM is the runtime engine, and bytecode portability is central to how Java works.
- Primitive types and reference types behave differently, and understanding heap versus stack is essential for reasoning about memory.
- Operators and control flow look simple, but many real bugs come from incorrect comparisons, truncation, bad branching, or poorly designed loops.
- Methods are the unit of reusable behavior, and clean method design has direct impact on readability, testing, and long-term maintenance.
- These fundamentals appear everywhere in real backend systems, so learning them deeply pays off before moving on to OOP, collections, concurrency, and frameworks.