# File 3: Core Java APIs

The Java language by itself is only part of the story. What makes Java productive in real work is the standard library. Once you move beyond syntax, you spend large amounts of time with `String`, collections, generics, exceptions, file and stream APIs, time APIs, and utility classes.

This file focuses on the APIs that show up constantly in real applications. These topics are foundational because they influence how data moves through the system, how failures are handled, and how code remains readable and type-safe at scale.

## Strings and Immutability

### Intuition

A `String` is one of the most frequently used objects in Java. It represents text such as user names, JSON fields, URLs, tokens, file paths, SQL fragments, log messages, and HTTP headers.

Java makes `String` immutable, which means once a string object is created, its contents do not change.

### Why Immutability Matters

Immutability makes strings:

- safer to share across threads
- easier to reason about
- more predictable as map keys and cache keys
- less error-prone in APIs that pass text through multiple layers

### Example

```java
String original = "order";
String upper = original.toUpperCase();

System.out.println(original); // order
System.out.println(upper);    // ORDER
```

`original` is unchanged. `toUpperCase()` returns a new `String`.

### Internal Detail That Matters

Java often interns string literals, meaning identical literals may refer to the same pooled object. That is an optimization detail, not something business logic should rely on.

This is why using `==` for strings is dangerous.

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

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

### Performance Concern

Repeated string concatenation inside loops can create many temporary objects.

```java
String result = "";
for (String item : items) {
    result += item;
}
```

Prefer `StringBuilder` for heavy concatenation.

```java
StringBuilder builder = new StringBuilder();
for (String item : items) {
    builder.append(item);
}
String result = builder.toString();
```

### Real-World Use Cases

- building log lines or audit entries
- parsing and validating incoming request fields
- constructing file paths, cache keys, and search queries
- formatting API responses or templated messages

### Common Pitfalls

- comparing strings with `==`
- forgetting that strings are immutable and assuming a method call mutates them
- building large strings inefficiently in loops

## Collections Framework

Collections are how Java code represents groups of related elements. In real systems, almost every workflow involves a collection somewhere: request headers, order items, search results, cache entries, event batches, deduplicated IDs, grouped metrics, or lookup tables.

### Collections Structure Diagram

```mermaid
flowchart TD
    A[Collection Framework] --> B[List]
    A --> C[Set]
    A --> D[Map]
    B --> E[ArrayList]
    B --> F[LinkedList]
    C --> G[HashSet]
    C --> H[TreeSet]
    D --> I[HashMap]
    D --> J[TreeMap]
```

### `List`

A `List` is ordered and allows duplicates.

```java
List<String> steps = new ArrayList<>();
steps.add("validate");
steps.add("charge");
steps.add("notify");
```

Use a list when order matters or when repeated values are acceptable.

#### `ArrayList`

This is the default `List` choice in most business applications. It provides fast indexed access and good general-purpose performance.

Typical use cases:

- ordered API results
- batched work items
- DTO lists in controller responses

#### `LinkedList`

Used far less often in typical backend applications than beginners expect. It can be helpful in queue-like patterns, but `ArrayList` and dedicated queue implementations are usually more practical.

### `Set`

A `Set` stores unique elements.

```java
Set<String> roles = new HashSet<>();
roles.add("ADMIN");
roles.add("USER");
roles.add("ADMIN");
```

The duplicate `ADMIN` is ignored.

Use a set when uniqueness matters.

Real-world examples:

- deduplicating email addresses
- tracking processed event IDs for idempotency checks
- storing permission names

### `Map`

A `Map` stores key-value pairs.

```java
Map<String, Integer> inventory = new HashMap<>();
inventory.put("laptop", 15);
inventory.put("mouse", 48);
```

Maps are everywhere in backend systems.

Typical examples:

- ID to entity lookup
- configuration name to value
- region to tax rule
- user ID to session metadata

### Choosing Common Implementations

| Type | Default Choice | Why |
| --- | --- | --- |
| `List` | `ArrayList` | Fast iteration and index-based reads |
| `Set` | `HashSet` | Fast uniqueness checks |
| `Map` | `HashMap` | Fast key-based lookup |

Choose tree-based variants like `TreeSet` or `TreeMap` when you explicitly need sorted ordering.

### Practical Usage Pattern

Suppose you receive a batch of orders and want to group them by customer.

```java
Map<String, List<Order>> ordersByCustomer = new HashMap<>();

for (Order order : orders) {
    ordersByCustomer
            .computeIfAbsent(order.getCustomerId(), key -> new ArrayList<>())
            .add(order);
}
```

This is a very common pattern in reporting, aggregation, and batch-processing pipelines.

### Pitfalls

- assuming `HashMap` preserves insertion order; it does not
- forgetting that mutable keys can break map behavior
- choosing a list when you really need uniqueness or fast lookup
- modifying a collection while iterating in unsupported ways, causing `ConcurrentModificationException`

## Generics

Generics allow Java to express type-safe reusable data structures and APIs.

### Intuition

Without generics, collections would hold generic `Object` values, forcing you to cast manually and discover mistakes late. Generics move those checks to compile time.

```java
List<String> names = new ArrayList<>();
names.add("Anita");
```

Now the compiler knows this list is supposed to contain strings.

### Why This Matters in Real Systems

Generics make API contracts clearer.

Examples:

- `List<Order>` means a list of orders, not arbitrary objects
- `Map<String, UserSession>` means string keys mapped to session objects
- `ResponseEntity<CustomerDto>` clearly communicates response payload type in a web application

### Generic Methods

```java
public static <T> T first(List<T> items) {
    if (items.isEmpty()) {
        throw new IllegalArgumentException("List cannot be empty");
    }
    return items.get(0);
}
```

### Wildcards at a High Level

You will eventually see forms such as `List<? extends Number>` or `List<? super Integer>`. The full details take practice, but the intuition is:

- `extends` is for reading from a producer
- `super` is for writing to a consumer

This is often summarized as PECS: producer extends, consumer super.

### Internal Note

Java generics use type erasure, meaning generic type information is mostly removed at runtime. This is why you cannot do everything with generics that reified generic systems in some other languages allow.

### Common Pitfalls

- using raw types like `List` instead of `List<String>`
- overcomplicating code with advanced wildcards when a simpler API would do
- expecting generic type arguments to remain fully available at runtime

## Exception Handling

Failure handling is a core part of Java engineering. Good code does not just describe the happy path. It makes failure modes visible and deliberate.

### The Idea Behind Exceptions

An exception represents a failure or abnormal condition that interrupts normal execution.

Some failures are expected operationally, such as missing files or invalid user input. Others represent programming bugs, such as null dereferences or invalid assumptions.

### Checked vs Unchecked Exceptions

Java has two broad exception categories.

#### Checked exceptions

These are enforced by the compiler. You must catch them or declare them.

Examples include many file and I/O related exceptions.

#### Unchecked exceptions

These extend `RuntimeException`. The compiler does not force handling.

Examples include:

- `NullPointerException`
- `IllegalArgumentException`
- `IllegalStateException`

### Why the Distinction Exists

Checked exceptions signal recoverable or expected external failure modes.

Unchecked exceptions often represent programming mistakes or invalid internal states.

In real engineering, teams vary on how much they like checked exceptions, but understanding the model is still important.

### Example

```java
public String readFile(Path path) throws IOException {
    return Files.readString(path);
}
```

The caller must decide whether to handle `IOException` or propagate it.

### Practical Guidance

- throw exceptions that communicate intent clearly
- do not catch exceptions just to ignore them
- wrap low-level exceptions when exposing a cleaner domain-level abstraction
- include useful context in logs and messages

### Real-World Pattern

Suppose a service calls a third-party payment gateway. The low-level HTTP client might throw transport exceptions, but your business layer may convert those into a domain-specific exception like `PaymentUnavailableException`.

That makes the rest of the system easier to understand.

### `try`, `catch`, `finally`, and try-with-resources

```java
try (BufferedReader reader = Files.newBufferedReader(path)) {
    return reader.readLine();
} catch (IOException exception) {
    throw new IllegalStateException("Failed to read config", exception);
}
```

The try-with-resources form is important because it closes resources automatically.

### Pitfalls

- catching `Exception` too broadly and hiding real issues
- using exceptions for normal control flow
- logging and rethrowing the same exception repeatedly, creating noisy duplicate logs
- losing the original cause when wrapping exceptions badly

## Java I/O Basics

I/O means interacting with systems outside your program's in-memory state.

Examples include:

- reading files
- writing logs
- handling network sockets
- streaming data to cloud storage
- loading configuration from disk

### Intuition

I/O is slower and less predictable than pure in-memory operations because it depends on disks, networks, operating system buffers, remote services, and external state.

That is why I/O-heavy code needs stronger error handling and performance awareness.

### Core Modern APIs

The `java.nio.file` package is the common modern entry point for file work.

```java
Path path = Path.of("config/app.properties");
String content = Files.readString(path);
Files.writeString(path, content + "\nmode=prod");
```

### Streams of Data

Buffered APIs are often used for efficient reading and writing.

```java
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}
```

### Production Use Cases

- loading service configuration or templates at startup
- ingesting CSV or log files in batch jobs
- exporting reports to disk or object storage
- reading secrets or certificates mounted into a container

### Common Pitfalls

- reading huge files into memory when streaming would be safer
- forgetting character encoding concerns
- failing to close resources properly
- performing blocking I/O on critical request threads without understanding throughput impact

## Practical Usage Patterns That Show Up Everywhere

### Pattern 1: Deduplication

Use a `Set` when idempotency or uniqueness matters.

```java
Set<String> processedEventIds = new HashSet<>();
```

### Pattern 2: Lookup by Key

Use a `Map` for fast retrieval.

```java
Map<Long, Customer> customersById = new HashMap<>();
```

### Pattern 3: Ordered Results

Use a `List` when sequence matters.

```java
List<Transaction> transactions = new ArrayList<>();
```

### Pattern 4: Safe Text Handling

Use immutable strings for identifiers, payload fragments, and logs, but switch to `StringBuilder` for heavy concatenation.

### Pattern 5: Resource Safety

Use try-with-resources for anything that must be closed, such as readers, streams, sockets, and many database-facing abstractions.

## How These APIs Show Up in Production Systems

Consider a batch job that processes order exports:

1. it reads a file using Java I/O APIs
2. it parses each line into objects and stores them in collections
3. it uses maps for lookups and sets for deduplication
4. it uses strings to validate and normalize text fields
5. it throws or wraps exceptions when bad data or file issues occur
6. it emits a summary report built safely and efficiently

This is ordinary Java engineering. It is not glamorous, but a large amount of production reliability depends on doing these basics well.

## Key Takeaways

- `String` is immutable, which improves safety and predictability, but you should still be mindful of comparison and concatenation costs.
- The collections framework gives you distinct tools for ordering, uniqueness, and lookup, and choosing the right one affects both clarity and performance.
- Generics move many type errors to compile time and make library and application code much easier to reason about.
- Exception handling is about making failure explicit and meaningful, not just preventing crashes.
- Java I/O interacts with slow and failure-prone external systems, so resource handling and error handling matter a lot.
- These APIs form the everyday vocabulary of production Java code, especially in backend services, batch jobs, and integration-heavy systems.