# Java Programming

> A comprehensive Java programming course covering syntax, types, control flow, methods, object-oriented programming, arrays, exceptions, file I/O, generics, collections, concurrency, the JVM, JDBC, and production behavior with executable examples.

- Author: Muhammet Ali Köker
- Language: en
- Canonical: https://alikoker.com.tr/en/java-programming
- Translation: https://alikoker.com.tr/java-programlama
- Published: 2013-09-30T12:00:00+03:00
- Modified: 2026-09-16T02:53:58+03:00
- Verified: 2026-09-16T02:53:58+03:00
- Type: article

Java separates the programming language from the virtual-machine execution model. That separation is central to portability, but portability does not mean that operating-system behavior, native libraries, encodings, hardware limits, or external-service dependencies disappear. Reliable Java systems require the language rules, Java Virtual Machine (JVM), standard libraries, and runtime behavior to be understood together.


Java programming is best understood as one continuum, from problem solving, types, input/output, control flow, methods, classes, arrays, inheritance, exceptions, and file I/O to the modern JVM, collections, generics, concurrency, data access, and production behavior. The goal is therefore not to memorize syntax, but to explain why a Java program behaves as it does from source code to runtime.

## Unit 1: Compilation, Bytecode, and the JVM

Compiling a `.java` source file with `javac` normally produces `.class` files containing JVM instructions rather than instructions for one processor family. The JVM loads, verifies, links, and executes class files. Frequently executed code can be compiled to native machine code by a just-in-time compiler. Java therefore does not fit the simplistic label of being only interpreted or only ahead-of-time compiled.

The JDK contains development tools, the compiler, and the runtime. In modern Java distributions the historical model of installing a separate JRE is no longer the main deployment abstraction; customized runtime images can also be produced with tools such as `jlink`. Source level, target runtime, library compatibility, and preview-feature use have to be managed as one versioning problem.

As of 15 September 2026, JDK 27 is the latest feature release and JDK 25 is the current LTS line. Production selection should be based on support lifetime, dependency compatibility, security updates, and organizational upgrade policy rather than on “latest” alone.

The principal JVM run-time data areas include per-thread program counters and JVM stacks, the heap that holds objects, class-related metadata and run-time constant pools, and structures required for native execution. Source-level concepts must not be mapped mechanically to physical memory addresses. A JVM may apply escape analysis, scalar replacement, and other optimizations that change how an allocation is realized internally without changing observable program semantics.


### First program, compilation, and execution

The execution entry point of a conventional Java application is the `public static void main(String[] args)` method. `main` is not a special JVM instruction; it is a `static` method with a signature recognized by the application launcher. Java is case-sensitive, so `total`, `Total`, and `TOTAL` are distinct identifiers.

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

If the source file is named `HelloJava.java`, the basic command chain is:

```text
javac HelloJava.java
java HelloJava
```

`import` is not a preprocessor directive in the C/C++ sense. It participates in compile-time name resolution so that types from other packages can be used without fully qualifying every occurrence. Types in `java.lang` are implicitly available; other packages require a fully qualified name or an `import`. A wildcard import such as `java.util.*` does not import subpackages.

Comments may use `//`, `/* ... */`, or `/** ... */` for API documentation. Source formatting does not alter runtime semantics, but consistent indentation that exposes block structure directly reduces maintenance cost.

## Unit 2: Types, Variables, and Expressions

Java uses static type checking. Its primitive types are `byte`, `short`, `int`, `long`, `float`, `double`, `char`, and `boolean`. Wrapper classes provide object representations for primitive values. Autoboxing is convenient, but it can introduce allocations, null behavior, and equality mistakes when used without understanding the conversion.

All Java argument passing is **by value**. An object is not passed “by reference”; the value of the reference is copied into the parameter. Code can mutate the referenced object when the API permits it, but assigning a new object to the parameter does not rebind the caller's variable.

Widening numeric conversions are generally implicit, whereas narrowing conversions require an explicit cast. Integer overflow does not automatically throw an exception. Critical arithmetic may use checked methods such as `Math.addExact` and `Math.multiplyExact`. Decimal business values that require exact base-10 semantics often need `BigDecimal`, with scale and rounding defined as part of the business contract.

`String` is immutable. `==` tests reference identity for objects, whereas `equals` expresses the logical equality contract defined by a class. Repeated dynamic concatenation in a loop is usually modeled more predictably with `StringBuilder` than by relying on repeated immutable-string construction.

`StringBuffer` offers a historically synchronized mutable-character API, while `StringBuilder` provides similar mutable construction without that synchronization overhead. Thread safety should be established at the real ownership boundary rather than selecting `StringBuffer` mechanically whenever multiple threads exist.


### Variables, constants, operators, and conversions

A variable is declared with a type before it is used. Local variables do not receive automatic default values; definite-assignment rules are checked by the compiler. Object fields, in contrast, are initialized to the default value of their type. `final` prevents reassignment of a variable but does not automatically make the referenced object immutable.

Integer division, overflow, and numeric promotion rules matter in arithmetic expressions. `5 / 2` evaluates to `2`, whereas `5.0 / 2` evaluates to `2.5`. `%` computes the remainder. The difference between prefix and postfix `++`/`--` becomes visible when the expression value is consumed in the same statement; avoiding complex side-effect expressions usually improves readability.

```java
public class TypesAndConversions {
    public static void main(String[] args) {
        int count = 7;
        double unitPrice = 12.50;
        double total = count * unitPrice;
        int rounded = (int) Math.round(total);
        int checkedSum = Math.addExact(1_500_000_000, 500_000_000);

        System.out.println(total);
        System.out.println(rounded);
        System.out.println(checkedSum);
    }
}
```

### Console I/O and Scanner

`System.out.print` does not append a line terminator, while `System.out.println` does. `Scanner` is a convenient teaching and application tool for formatted console input. Token-oriented calls such as `nextInt()` and line-oriented `nextLine()` have different consumption rules; when mixed, the remaining line terminator must be accounted for.

```java
import java.util.Scanner;

public class ConsoleInput {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            System.out.print("Name: ");
            String name = scanner.nextLine().trim();

            System.out.print("First number: ");
            double a = Double.parseDouble(scanner.nextLine());

            System.out.print("Second number: ");
            double b = Double.parseDouble(scanner.nextLine());

            System.out.printf("Total for %s = %.2f%n", name, a + b);
        }
    }
}
```

### String: content versus identity

`String` is a class rather than a primitive type and is immutable. Operations such as `length`, `charAt`, `substring`, `indexOf`, `startsWith`, `endsWith`, `replace`, `trim`/`strip`, `toLowerCase`, and `toUpperCase` produce results without mutating the original string. Use `equals`/`equalsIgnoreCase` for content equality and `compareTo` for lexical ordering; `==` compares reference identity.

```java
public class TextProcessing {
    public static void main(String[] args) {
        String raw = "  Java Programming  ";
        String clean = raw.strip();
        String lower = clean.toLowerCase();

        StringBuilder builder = new StringBuilder(32);
        builder.append(lower).append(" | length=").append(clean.length());

        System.out.println(builder);
        System.out.println("java programming".equalsIgnoreCase(clean));
    }
}
```


## Unit 3: Decision Structures and Loops

Control flow determines which statements an algorithm executes and in what order. `if`/`else` handles general predicates, `switch` fits discrete value sets, and the conditional operator (`?:`) provides compact value selection. A modern `switch` expression requires branches to produce a value, which can reduce some classes of accidental fall-through errors.

```java
public class DecisionStructure {
    static String grade(int score) {
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("Score must be in 0..100");
        }

        int group = score / 10;
        return switch (group) {
            case 10, 9 -> "A";
            case 8 -> "B";
            case 7 -> "C";
            case 6 -> "D";
            default -> "F";
        };
    }

    public static void main(String[] args) {
        System.out.println(grade(86));
    }
}
```

`while` checks its condition before the body, whereas `do-while` checks after it. `do-while` is useful when the body must execute at least once. `for` groups initialization, condition, and update; enhanced `for` traverses arrays and `Iterable` values when explicit indexing is unnecessary. `break` terminates a loop and `continue` advances to the next iteration.

A sentinel-controlled loop is a classic pattern when the input length is not known in advance:

```java
import java.util.Scanner;

public class SentinelAverage {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            long total = 0;
            int count = 0;

            while (true) {
                int value = scanner.nextInt();
                if (value == -999) {
                    break;
                }
                total += value;
                count++;
            }

            if (count > 0) {
                System.out.println((double) total / count);
            }
        }
    }
}
```

Random-number generation should also consider testability. `Random`, `ThreadLocalRandom`, and `SecureRandom` address different requirements; `Math.random()` is not a universal choice for every random-number use case.


## Unit 4: Methods, static, Math, and Overloading

A method names a behavior, may receive parameters, and returns a result unless its return type is `void`. Java always passes arguments by value. For an object argument, the copied value is a reference; the method may mutate the referenced object but cannot rebind the caller's variable.

```java
public class MethodExample {
    static int max(int a, int b) {
        return a >= b ? a : b;
    }

    static double average(int... values) {
        if (values.length == 0) {
            throw new IllegalArgumentException("At least one value is required");
        }
        long total = 0;
        for (int value : values) {
            total += value;
        }
        return (double) total / values.length;
    }

    public static void main(String[] args) {
        System.out.println(max(7, 11));
        System.out.println(average(10, 20, 30, 40));
    }
}
```

A `static` member belongs to the class; an instance member requires an object. Helpers such as `Math.sqrt`, `Math.pow`, `Math.abs`, `Math.min`, and `Math.max` are static methods. Wrapper classes (`Integer`, `Long`, `Double`, `Character`, and others) connect primitive values with object APIs. Conversions such as `Integer.parseInt("42")` are common when converting text input to numeric values.

Overloading reuses a method name with different parameter lists and is resolved at compile time:

```java
public class OverloadExample {
    static long area(int side) {
        return (long) side * side;
    }

    static long area(int width, int height) {
        return (long) width * height;
    }

    static double area(double radius) {
        return Math.PI * radius * radius;
    }

    public static void main(String[] args) {
        System.out.println(area(5));
        System.out.println(area(4, 7));
        System.out.println(area(2.5));
    }
}
```

## Unit 5: Classes and Object-Oriented Programming

A Java class can contain fields, methods, constructors, nested types, and initialization blocks. Abstraction, encapsulation, inheritance, and polymorphism are common object-oriented concepts, but maintainable design is not achieved by maximizing inheritance or the number of classes. The important question is where change and responsibility boundaries belong.

Access is controlled by `private`, package access, `protected`, and `public`. Exposing everything publicly weakens invariants. Immutable objects validate their state at construction time, avoid exposing mutable internal representation, and do not change observable state during their lifetime.

Method **overloading** is resolved from different parameter lists at compile time. **Overriding** changes inherited instance behavior and participates in run-time polymorphism. Static methods, private methods, and constructors have different dispatch rules and should not be described as ordinary overriding.

An abstract class can hold state and implemented behavior. An interface defines a contract and, in modern Java, can also contain default, static, and private helper methods. A class extends one class but can implement multiple interfaces. Inheritance is appropriate only when the subtype preserves the behavioral contract of the supertype; composition often produces lower coupling when behavior can be assembled from collaborators.

Records provide a compact model for data-oriented classes, including component accessors and standard `equals`, `hashCode`, and `toString` behavior. Enums are class-like types rather than integer constants. Sealed classes constrain which types may extend or implement a hierarchy.


### Encapsulation, constructors, and this

Internal state should be managed through invariants protected by the class rather than exposed as raw fields; `public`/`private`, constructors, and carefully chosen accessors are mechanisms for enforcing that boundary. Encapsulation does not require a setter for every field; immutable or controlled-mutation designs often provide a stronger contract by exposing only meaningful operations.

```java
public class AccountExample {
    static final class BankAccount {
        private final String iban;
        private long balanceCents;

        BankAccount(String iban, long initialBalanceCents) {
            if (iban == null || iban.isBlank()) {
                throw new IllegalArgumentException("IBAN must not be blank");
            }
            if (initialBalanceCents < 0) {
                throw new IllegalArgumentException("Balance must not be negative");
            }
            this.iban = iban;
            this.balanceCents = initialBalanceCents;
        }

        void deposit(long amountCents) {
            if (amountCents <= 0) {
                throw new IllegalArgumentException("Amount must be positive");
            }
            balanceCents = Math.addExact(balanceCents, amountCents);
        }

        long balanceCents() {
            return balanceCents;
        }

        String iban() {
            return iban;
        }
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount("TR0001", 10_000);
        account.deposit(2_500);
        System.out.println(account.iban() + " " + account.balanceCents());
    }
}
```

If a class declares no constructor, the compiler supplies a no-argument default constructor. Once the programmer declares any constructor, that implicit constructor is no longer generated. `this(...)` invokes another constructor in the same class, while `super(...)` invokes a superclass constructor; either invocation must be the first statement in the constructor body.

### Inheritance, abstract classes, interfaces, and polymorphism

Inheritance uses `extends`; interface implementation uses `implements`. Overriding preserves the parameter contract, and dynamic dispatch selects the instance method according to the actual runtime type. `@Override` helps the compiler verify programmer intent.

```java
public class PolymorphismExample {
    interface ConsumesEnergy {
        double consumptionKWh(double km);
    }

    static abstract class Vehicle {
        private final String brand;

        Vehicle(String brand) {
            this.brand = brand;
        }

        String brand() {
            return brand;
        }

        abstract String kind();
    }

    static final class ElectricCar extends Vehicle implements ConsumesEnergy {
        ElectricCar(String brand) {
            super(brand);
        }

        @Override
        String kind() {
            return "Electric car";
        }

        @Override
        public double consumptionKWh(double km) {
            return km * 0.17;
        }
    }

    public static void main(String[] args) {
        Vehicle vehicle = new ElectricCar("Example");
        System.out.println(vehicle.brand() + " - " + vehicle.kind());
    }
}
```

A superclass's `private` fields are not directly visible to subclasses; access should go through the superclass contract. A `final` method cannot be overridden and a `final` class cannot be subclassed. Building inheritance hierarchies merely to reuse code is fragile; composition is preferable when substitutability does not hold.

## Unit 6: The Object Contract, Equality, and Identity

`Object` is the common root of the ordinary class hierarchy. If a class defines logical equality with `equals`, it must preserve the corresponding `hashCode` contract: equal objects must produce equal hash codes. The converse is not required because hash collisions are valid.

Mutating state that participates in equality or hashing while an object is used as a hash-based key can make an entry appear unreachable. Keys should therefore be immutable, or at least stable with respect to equality while stored in the map or set.

`toString` is useful for diagnostics but is not a persistent serialization format. Passwords, tokens, national identifiers, and other sensitive fields must not leak through generated `toString` methods, logs, or exception messages.


### Concrete equals, hashCode, and toString example

`==` and `equals` express different equality semantics. When logical equality is defined, `hashCode` must be based on the same equality state.

```java
import java.util.Objects;

public final class Fraction {
    private final int numerator;
    private final int denominator;

    public Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException("Denominator must not be zero");
        }
        int gcd = gcd(Math.abs(numerator), Math.abs(denominator));
        int sign = denominator < 0 ? -1 : 1;
        this.numerator = sign * numerator / gcd;
        this.denominator = sign * denominator / gcd;
    }

    private static int gcd(int a, int b) {
        while (b != 0) {
            int t = a % b;
            a = b;
            b = t;
        }
        return a == 0 ? 1 : a;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Fraction other)) return false;
        return numerator == other.numerator && denominator == other.denominator;
    }

    @Override
    public int hashCode() {
        return Objects.hash(numerator, denominator);
    }

    @Override
    public String toString() {
        return numerator + "/" + denominator;
    }
}
```

## Unit 7: Exceptions and Resource Management

Java exceptions form a hierarchy below `Throwable`, with `Error` and `Exception` as major branches. Checked versus unchecked is primarily a compile-time catch-or-declare distinction. It is not a reliable rule that every checked exception is recoverable or every unchecked exception is necessarily a programmer defect.

Exception boundaries should align with abstraction boundaries. Replacing every low-level error with a generic exception destroys causal information; exposing implementation-specific exceptions through a public API can also break encapsulation. When translating exceptions, retaining the original cause preserves diagnostic context.

`try-with-resources` provides deterministic closing for `AutoCloseable` resources such as files, sockets, and JDBC objects, even when an exception occurs. `finally` remains useful for general cleanup, but resource lifetime is usually clearer with the dedicated construct.

`final`, `finally`, and the historical `finalize()` mechanism are unrelated concepts. Finalization is not a dependable resource-release mechanism and should be treated as obsolete in modern Java. External resources require explicit, deterministic lifecycle management.

Common run-time exceptions should be distinguished by cause rather than memorized as a flat list. `NullPointerException` denotes use of a null reference where an object is required; `ArithmeticException` can arise from operations such as integer division by zero; `NumberFormatException` signals invalid textual numeric conversion; `ClassCastException` indicates an invalid reference-type cast; and `ArrayIndexOutOfBoundsException` indicates an array index outside its valid range. `InterruptedException` is different: it participates in cooperative interruption of blocking operations and should not be swallowed without an explicit policy. `RuntimeException` is the common unchecked-exception base for many programming and contract violations, not a synonym for every failure at run time.


### Custom exceptions and multiple catch clauses

An exception may represent not only an unexpected technical failure but also violation of an API contract. Exception types should be meaningful and handled at the abstraction boundary that can decide what to do next.

```java
public class ExceptionExample {
    static final class InvalidDivisionException extends Exception {
        InvalidDivisionException(String message) {
            super(message);
        }
    }

    static int divide(int dividend, int divisor) throws InvalidDivisionException {
        if (divisor == 0) {
            throw new InvalidDivisionException("Divisor must not be zero");
        }
        return dividend / divisor;
    }

    public static void main(String[] args) {
        try {
            System.out.println(divide(10, 2));
        } catch (InvalidDivisionException e) {
            System.err.println(e.getMessage());
        }
    }
}
```

One `try` block may have multiple `catch` clauses; more specific subtypes must precede broader supertypes. Multi-catch such as `catch (IOException | SQLException e)` reduces duplication when unrelated exception types receive the same handling. Nested `try-catch` is valid, but if it obscures control flow, redesigning method boundaries is usually cleaner.

## Unit 8: Generics and Type Safety

Generics move many cast errors from run time to compile time. Java's generic implementation largely uses type erasure, which means `List<String>` and `List<Integer>` are not separate reified classes at run time.

Wildcards express variance relationships. `? extends T` is useful when an API primarily produces `T` values, while `? super T` is useful when it consumes them. The familiar “producer extends, consumer super” rule is a useful design heuristic, not a replacement for understanding the actual mutation contract.

Raw types should normally be limited to legacy interoperability. Unchecked casts, generic varargs, and reflective operations can undermine type safety and create heap-pollution failures that appear far from the original unsafe operation.


### Arrays and multidimensional arrays

An array stores a fixed number of values of one component type and exposes them through indexes. The `length` field reports the number of elements. Because an array variable holds an object reference, `b = a` does not copy elements; it binds both variables to the same array. Use `Arrays.copyOf`, `clone`, or explicit element copying when an independent copy is required.

```java
import java.util.Arrays;

public class ArrayExample {
    static double average(int[] values) {
        if (values.length == 0) {
            return Double.NaN;
        }
        long total = 0;
        for (int value : values) {
            total += value;
        }
        return (double) total / values.length;
    }

    public static void main(String[] args) {
        int[] scores = {72, 91, 64, 88, 79};
        int[] copy = Arrays.copyOf(scores, scores.length);
        Arrays.sort(copy);

        int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6}
        };

        System.out.println(average(scores));
        System.out.println(Arrays.toString(copy));
        System.out.println(matrix[1][2]);
    }
}
```

A type such as `int[][]` is actually an array of array references; rows are not required to have identical lengths. This permits jagged structures. For large numerical matrices, however, object/array layout, locality, and memory cost should be evaluated explicitly.


### Simple generic method example

```java
import java.util.List;

public class GenericExample {
    static <T> T first(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("List is empty");
        }
        return list.get(0);
    }

    public static void main(String[] args) {
        System.out.println(first(List.of("Java", "JVM")));
        System.out.println(first(List.of(10, 20, 30)));
    }
}
```

## Unit 9: Collections

`Collection` covers list, set, and queue-oriented abstractions; `Map` represents key-value associations separately. Choosing a collection requires considering algorithmic complexity, memory behavior, ordering requirements, mutation patterns, and concurrency rather than only API familiarity.

- `ArrayList` provides efficient indexed access and amortized constant-time append, while insertion in the middle can shift elements.
- `LinkedList` is a doubly linked list and also a `Deque`; indexed access is linear.
- `HashSet` and `HashMap` are general-purpose hash structures and do not promise a logical iteration order.
- `LinkedHashMap` can preserve insertion or access order.
- `TreeSet` and `TreeMap` provide comparator-based ordering.
- `ArrayDeque` is generally preferable to the legacy `Stack` class for stack and deque behavior.
- `PriorityQueue` exposes priority at the head; it is not a permanently sorted list representation.

`HashMap` is not thread-safe. `Hashtable` is a legacy synchronized class. New concurrent designs should choose `ConcurrentHashMap` or explicit synchronization according to the operation-level consistency contract. A concurrent map should not be mentally modeled as a table protected by one global lock.

Iterator behavior varies by collection. Many classic collections use fail-fast checks; some concurrent collections provide weakly consistent iteration. `ConcurrentModificationException` is a bug-detection aid, not a concurrency-safety guarantee.

More specialized collection types encode additional contracts. `ListIterator` can traverse lists in both directions and supports position-aware updates; `LinkedHashSet` preserves encounter order while retaining set semantics; `EnumSet` is a compact set specialized for enum constants; and `NavigableMap` adds nearest-key and range-navigation operations to sorted maps. `IdentityHashMap` compares keys by reference identity rather than ordinary `equals`, so it is suitable only for identity-oriented algorithms. `CopyOnWriteArrayList` favors read-heavy workloads by copying its backing array on mutation; using it in write-heavy paths can cause substantial allocation and copying cost.


### Seeing collection selection in code

```java
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CollectionExample {
    public static void main(String[] args) {
        List<String> languages = List.of("Java", "C", "C++", "C#");

        Map<String, Integer> lengths = new HashMap<>();
        for (String language : languages) {
            lengths.put(language, language.length());
        }

        ArrayDeque<String> queue = new ArrayDeque<>();
        queue.addLast("first");
        queue.addLast("second");

        System.out.println(lengths.get("Java"));
        System.out.println(queue.removeFirst());
    }
}
```

## Unit 10: Lambdas, Functional Interfaces, and Streams

A lambda is targeted to a functional interface with one abstract method. Common examples include `Predicate`, `Function`, `Consumer`, and `Supplier`, together with primitive specializations. Method references adapt existing methods to compatible functional-interface targets.

The `@FunctionalInterface` annotation documents and compiler-checks that an interface is intended to satisfy the single-abstract-method contract. It is not required for an interface to be a functional interface, but it protects the intent against accidentally adding a second abstract method.

A stream is a processing pipeline, not a data store. Intermediate operations such as `map`, `filter`, `flatMap`, `sorted`, and `distinct` are generally lazy and execute only when a terminal operation demands results.

A parallel stream is not automatically faster because multiple CPU cores exist. Splitting cost, common-pool contention, data size, work per element, ordering, side effects, and shared resources must be measured. Blind parallelization of blocking I/O or lock-heavy code can increase latency instead of reducing it.


### Stream example

```java
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> values = List.of(3, 8, 11, 14, 21, 30);

        int total = values.stream()
            .filter(x -> x % 2 == 0)
            .mapToInt(Integer::intValue)
            .sum();

        System.out.println(total);
    }
}
```

A simple `for` loop can perform the same computation. The justification for a stream is not merely fewer characters; it is the ability to read the transformation as a data pipeline. Allocation, boxing, and parallelism costs on hot paths should still be measured.

## Unit 11: I/O, NIO, and Serialization

`java.io` contains classic stream-oriented APIs, while `java.nio` and `java.nio.file` provide buffers, channels, charsets, and modern filesystem access. Text conversion should specify a charset explicitly. Relying on the platform default can create silent data corruption when an application moves between environments.

Classic I/O classes still appear in existing code and illustrate the byte/character distinction. `FileInputStream` and `FileOutputStream` operate on bytes; `FileReader` and `FileWriter` are character-oriented; `BufferedReader` adds buffered text reading and convenient line-oriented access; `PrintStream` and `PrintWriter` provide formatted textual output with different underlying character/byte semantics. Resource ownership and charset behavior should be explicit instead of inferred from a class name.

`Path` and `Files` are the main modern filesystem abstractions. Large data should often be streamed or processed in chunks instead of being loaded entirely into heap memory. Where durable publication matters, writing a temporary file and atomically moving it into place can prevent readers from observing partially written output.

Built-in Java object serialization still exists in legacy systems, but deserializing untrusted object graphs is a serious security boundary. New external data contracts should prefer explicit, constrained formats and schemas.

For XML processing, SAX-style parsing streams events and can keep memory use bounded for large documents, whereas DOM builds an in-memory tree that is convenient for random navigation but proportional to document size. The choice is therefore an access-pattern and resource trade-off, not a universal preference.


### Text file I/O: from classic streams to the modern Files API

`PrintWriter`, `BufferedReader`, `FileReader`, and `File` remain valid text-I/O building blocks. Modern code often uses `Path` and `Files` for a clearer filesystem model, and character encoding should not be left to an implicit platform default.

```java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class FileExample {
    public static void main(String[] args) throws Exception {
        Path path = Path.of("example.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
            writer.write("Java");
            writer.newLine();
            writer.write("JVM");
        }

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

`File` is the historical path/file abstraction; `Path`/`Files` exposes richer filesystem and error semantics. The distinction remains fundamental: `InputStream`/`OutputStream` for bytes and `Reader`/`Writer` for characters. Large files should be streamed, channeled, or processed in bounded chunks rather than loaded entirely into memory.

## Unit 12: Annotations, Reflection, and Modules

Annotations carry metadata with source, class-file, or run-time retention. Frameworks use them for mapping, configuration, and validation. An annotation does not execute behavior by itself; a compiler, annotation processor, framework, or reflective component must interpret it.

Reflection can inspect and invoke types, members, and annotations at run time. It is powerful but can weaken static type checking and encapsulation and may complicate performance or native/AOT processing. Explicit registration or build-time metadata generation can be more predictable in systems where dynamic discovery is unnecessary.

The Java Platform Module System uses `module-info.java` to describe module dependencies and exported packages. Module, package, and JAR are different abstractions.

`@Deprecated` marks an API whose continued use is discouraged and whose replacement or migration guidance should normally be inspected; it does not mean the API immediately stops functioning. External tools such as Lombok can generate code at compile time; for example, `@SuperBuilder` is Lombok-specific and is not a Java or Jakarta standard annotation. The origin and generated behavior of such annotations should be explicit in IDE, annotation-processing, and build pipelines.

## Unit 13: Concurrency and the Java Memory Model

Creating threads introduces visibility, atomicity, and ordering problems in addition to possible parallel execution. The Java Memory Model defines the conditions under which writes in one thread become visible to another. The `happens-before` relation is central to reasoning about this behavior.

`synchronized` provides mutual exclusion and memory-visibility effects around monitor acquisition and release. `volatile` establishes visibility and ordering rules for reads and writes of a variable, but it does not make a compound read-modify-write operation such as `count++` atomic. Atomic variables or structures such as `LongAdder` may be appropriate depending on contention and required semantics.

The `Lock` APIs add features such as timed acquisition, interruptible acquisition, and multiple conditions. With multiple locks, a consistent acquisition order, short critical sections, and avoiding blocking I/O while holding locks are important deadlock controls.

`ExecutorService` separates task submission from thread lifecycle. Pool sizing cannot be reduced to a single formula such as “number of CPU cores.” CPU versus I/O behavior, downstream capacity, queue bounds, deadlines, and target latency all matter.

`CompletableFuture` composes asynchronous operations, but executor selection, exception propagation, cancellation, and timeouts must be explicit. Virtual threads make large numbers of blocking tasks cheaper to represent; they do not create more database connections, remote-service quota, or lock capacity.

Some newer concurrency APIs, including forms of structured concurrency, may be preview features depending on the JDK release. Preview APIs should not be treated as permanently stable without checking the specification of the actual deployment JDK.


### Atomic counter and virtual-thread example

```java
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

public class ConcurrencyExample {
    public static void main(String[] args) throws Exception {
        AtomicInteger counter = new AtomicInteger();
        List<Future<?>> tasks = new ArrayList<>();

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 1_000; i++) {
                tasks.add(executor.submit(counter::incrementAndGet));
            }
            for (Future<?> task : tasks) {
                task.get();
            }
        }

        System.out.println(counter.get());
    }
}
```

The atomic counter prevents a data race in this example. A virtual thread reduces task-representation cost; it does not imply that 1,000 concurrent database queries are safe. External resource capacity still requires explicit bounds.

## Unit 14: JVM Memory, Garbage Collection, and JIT Compilation

Objects that are no longer strongly reachable become candidates for garbage collection; assigning a local variable to `null` does not mean the object is immediately deleted. Collector choice, heap sizing, allocation rate, live-set size, and latency objectives form one tuning problem.

Memory leaks are possible in managed languages. If an object is no longer useful to the application but remains reachable through a strong reference, the collector cannot reclaim it. Unbounded caches, listener registrations, `ThreadLocal` lifetimes, and long-lived collections are common sources.

JIT compilation introduces warm-up effects. A Java microbenchmark should not simply wrap a few method calls with `System.nanoTime()`. Dead-code elimination, constant folding, tiered compilation, and garbage collection can invalidate naive measurements. JMH is designed to address many of these hazards.

Older JVM material may refer to **PermGen** (permanent generation). HotSpot removed PermGen in Java 8 and moved class metadata to **Metaspace**, which is managed differently and is not a reason to carry historical PermGen tuning advice into current JVM deployments.

## Unit 15: JDBC and Data Access

JDBC is Java's standard database-access API. `Connection`, `PreparedStatement`, and `ResultSet` are core building blocks. Parameter binding is preferable to concatenating untrusted values into SQL, both for type handling and for reducing SQL-injection exposure.

`CallableStatement` is the JDBC contract for invoking stored procedures or database routines that expose callable semantics. Its use does not remove transaction, timeout, type-mapping, or resource-lifetime concerns; those remain part of the surrounding JDBC contract.

Transaction boundaries should represent business atomicity. Multi-step writes performed without understanding auto-commit can leave partial results. A connection pool amortizes connection-establishment cost, but its size must respect the database's real concurrent capacity.

ORM and JPA do not remove the need to understand SQL and transactions. N+1 access patterns, broad transactions, inappropriate fetching, unnecessary entity materialization, and unbounded result sets can dominate application cost. The performance side is developed further in [High-Performance Java Data Systems](/en/high-performance-java-data-systems).

Hibernate commonly exposes a `SessionFactory`/`Session` model, while standard JPA uses `EntityManagerFactory`/`EntityManager`. HQL is Hibernate's object-oriented query language; JPQL is the standardized persistence query language. Mapping annotations such as `@ManyToOne`, `@OneToMany`, and `@GeneratedValue` describe relationships or identifier generation, while `FetchType.LAZY` and `FetchType.EAGER` express fetching intent rather than a universal performance rule. Accessing an uninitialized lazy association after its persistence context is unavailable can surface as Hibernate's `LazyInitializationException`; the design response is to define transaction and fetch boundaries deliberately, not to make every association eager.

Older JDBC examples may explicitly load a driver with `Class.forName(...)` and execute a query through `Statement` or `PreparedStatement.executeQuery(...)`. Modern JDBC drivers are commonly discovered through the service-provider mechanism, so `Class.forName` is not a universal startup requirement. `executeQuery` is intended for operations that return a result set; update and DDL operations follow different execution contracts.


### Parameterized query with PreparedStatement

```java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcExample {
    static String userName(Connection connection, long id) throws SQLException {
        String sql = "select name from users where id = ?";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next() ? rs.getString(1) : null;
            }
        }
    }
}
```

Parameter binding is not only about reducing SQL-injection exposure; it also makes type conversion and the query contract explicit. Transaction and timeout management are intentionally outside this small example; real applications must define connection lifetime and business atomicity separately.

## Unit 16: Networking, HTTP, and the Enterprise Java Ecosystem

The standard library includes socket and HTTP client APIs. TCP is a byte-stream transport; UDP is datagram-oriented. Application protocols still need framing, timeout, retry, authentication, and resource-limit rules.

Servlets and JSP are historically important Java web technologies and remain present in existing systems. In current Jakarta EE terminology their specification successors are Jakarta Servlet and Jakarta Pages; legacy `javax.*` and current `jakarta.*` namespaces are not interchangeable. Modern service applications often use higher-level frameworks such as Spring MVC, Spring Boot, or Jakarta REST. Framework knowledge does not replace language and runtime knowledge: class loading, collections, threads, I/O, and exception behavior become visible during production failures.

At the servlet API level, `HttpServlet` models HTTP-specific request handling; `ServletConfig` exposes initialization information for one servlet; and `ServletContext` represents application-wide servlet-container context and shared resources. JSTL (Jakarta Standard Tag Library, historically JavaServer Pages Standard Tag Library) supplies standard tags for page-layer concerns. These APIs are relevant when maintaining server-rendered Jakarta Pages/JSP systems even when newer applications use different presentation architectures.

On the desktop, AWT and Swing are established toolkits within Java SE. JavaFX is developed separately from the JDK through the OpenJFX project in modern releases and provides scene, layout, and control APIs for graphical applications. The existence of a Swing or JavaFX system is not by itself a reason to rewrite it as a web application; deployment, maintenance, and interaction requirements should drive that decision.

The Spring ecosystem is covered separately in [Spring Boot](/en/spring-boot), while browser and protocol concerns are discussed in [Web Programming](/en/web-programming).

In older servlet-based Java web applications, the `web.xml` deployment descriptor was a central XML mechanism for servlet, filter, listener, and mapping configuration. Annotation-based configuration has reduced many of its uses, but `web.xml` remains a relevant historical and compatibility mechanism in existing Jakarta Servlet/JSP systems and for selected container settings.


### Standard HttpClient example

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(2))
            .build();

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com"))
            .timeout(Duration.ofSeconds(3))
            .GET()
            .build();

        HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

        System.out.println(response.statusCode());
    }
}
```

A real service client should define request timeouts in addition to connection timeouts; retries should only be introduced with explicit knowledge of idempotency and failure classes. The HTTP client example does not replace networking fundamentals: connection pooling, DNS, TLS, proxies, and server capacity all contribute to end-to-end behavior.

## Unit 17: Builds, Dependencies, Version Control, and Testing

Maven and Gradle are not merely download tools. They model dependency resolution, build lifecycles, tests, packaging, and plugin execution. Uncontrolled transitive dependencies can cause behavioral differences, reproducibility failures, or security exposure.

JUnit is a core tool for Java unit and integration testing. Mockito and similar libraries can replace selected collaborators with controlled test doubles. Mocking every class is not a sign of good isolation; pure computation code is often clearer and more robust when tested with real objects.

Git provides distributed source version control. Commits, branches, and merges preserve source history and change context. CI/CD makes build, test, and delivery processes repeatable, but automation cannot compensate for an invalid test oracle or a dangerous deployment policy.

In classic JavaFX code, `VBox` is a vertical layout container, `ScrollBar` is a scrolling control, and `NumberConverter` belongs to the converter family used between numeric values and text. These are JavaFX API examples rather than Java language features. Knowing individual widget classes does not replace understanding the layout, threading, and lifecycle model.

## Unit 18: Secure and Measurable Java

Secure Java development requires more than catching exceptions. Input validation, authorization, query parameterization, safe deserialization, correct cryptographic APIs, secret handling, and dependency governance are parts of the same trust boundary.

Performance work should begin with the system objective: latency, throughput, CPU, allocation, GC pause, database capacity, or an external-service bottleneck. A collection micro-optimization may be irrelevant next to an inefficient query or a remote call performed while holding a lock. Profiling, Java Flight Recorder, GC information, thread dumps, and system metrics should identify the real constraint before code is changed.

The language, JVM, and frameworks are separate layers. Avoiding category errors between language guarantees, JVM implementation details, and framework conventions is one of the most important disciplines in production Java engineering.

## Value-oriented modeling with records, sealed types, and patterns

Java is not limited to mutable class hierarchies. Records provide compact value-oriented models with generated construction, accessors, equality, hashing, and string representation. Sealed types restrict which subtypes may exist.

Combined with pattern matching, these features can model closed domains explicitly. A result known to be one of a finite set of cases can be handled exhaustively.

The goal is not merely shorter code. It is to make the valid state space visible to the compiler. Open-ended domains should remain open rather than being forced into sealed hierarchies.

## References

- Oracle. *Java Platform, Standard Edition Documentation*. https://docs.oracle.com/en/java/javase/
- Oracle. *Java Language Specification*. https://docs.oracle.com/javase/specs/
- Oracle. *Java Virtual Machine Specification*. https://docs.oracle.com/javase/specs/
- Oracle. *Java SE API Documentation*. https://docs.oracle.com/en/java/javase/27/docs/api/
- Brian Goetz et al. *Java Concurrency in Practice*. Addison-Wesley, 2006.
- Joshua Bloch. *Effective Java*, 3rd ed. Addison-Wesley, 2018.
- OpenJFX. *JavaFX Documentation*. https://openjfx.io/
- Eclipse Foundation. *Jakarta Servlet Specification*. https://jakarta.ee/specifications/servlet/
- Eclipse Foundation. *Jakarta Pages Specification*. https://jakarta.ee/specifications/pages/
- OpenJDK. *JEP 444: Virtual Threads*. https://openjdk.org/jeps/444
- OpenJDK. *Java Microbenchmark Harness (JMH)*. https://openjdk.org/projects/code-tools/jmh/
- Eclipse Foundation. *Standard Widget Toolkit (SWT)*. https://www.eclipse.org/swt/
- A. Yazıcı, E. Doğdu, M. Özbayoğlu, M. Erten, O. Ergin. *Java Bilgisayar Programlamaya Giriş*.
- Walter Savitch, Frank M. Carrano. *Java: An Introduction to Problem Solving & Programming*, 5th ed. Pearson Education.
- P. Deitel, H. Deitel. *Java How to Program*. Pearson Education.

## Cite This Work

Köker, M. A. (2013). Java Programming. alikoker.com.tr. https://alikoker.com.tr/en/java-programming

- BibTeX: https://alikoker.com.tr/en/java-programming.bib
- RIS: https://alikoker.com.tr/en/java-programming.ris
- CSL-JSON: https://alikoker.com.tr/en/java-programming.csl.json
