Java is a statically-typed, class-based, compiled-to-bytecode language that runs on the Java Virtual Machine (JVM). It powers Android, large enterprise backends (Spring), big-data tools (Hadoop, Kafka), and most banking infrastructure. Part 1 of this two-part series covers the language core: from "hello world" through OOP, generics, exceptions, file I/O, and the basics of multithreading. Part 2 picks up with GUI, events, functional programming, collections, and the Stream API.


Learning Outcome

  • Compile and run Java programs with javac + java (and via an IDE).
  • Use primitives, wrappers, control flow, arrays, and strings idiomatically.
  • Define classes with constructors, fields, methods, access modifiers, and static.
  • Use inheritance, polymorphism, abstract classes, and interfaces.
  • Apply solid OOD principles (SOLID, encapsulation, composition over inheritance).
  • Write and use generic classes + methods.
  • Handle errors with try / catch / finally / throws and custom exceptions.
  • Use assert for development-time invariants.
  • Read and write files using the modern java.nio.file API.
  • Run code on multiple threads and recognise the standard concurrency utilities.

Chapter 1 — Introduction

Terminology

  • JDK — Java Development Kit: compiler + standard library + tools.
  • JRE — Java Runtime Environment: enough to run compiled code (rarely separated from JDK anymore).
  • JVM — Java Virtual Machine: executes bytecode.
  • Bytecode — the .class file format produced by javac.
  • Class — the template for objects.
  • Object — an instance of a class.
  • Package — a namespace; maps to a directory.
  • Main method — the JVM entry point: public static void main(String[] args).

Install

OS Recommended
Any Temurin (Eclipse Adoptium build of OpenJDK) — LTS 21 in 2026
macOS brew install --cask temurin
Windows winget install EclipseAdoptium.Temurin.21.JDK
Linux sudo apt install openjdk-21-jdk (or distribution equivalent)

Verify:

1
2
3
java --version
javac --version

Or use an IDE — IntelliJ IDEA Community is the standard pick.

Hello, world

Hello.java:

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

Compile + run:

1
2
3
javac Hello.java        # produces Hello.class
java Hello # runs the main method

Class name must match filename. One public class per file.

Modern Java (21+) supports a simplified compilation for single-file programs:

1
2
java Hello.java         # one step, no .class on disk

Useful for scripts and learning.


Chapter 2 — Objects and Classes

Terminology

  • Field — class-level variable (state).
  • Method — class-level function (behaviour).
  • Constructor — special method called when new creates an instance.
  • this — reference to the current instance inside a method.
  • static — belongs to the class, not an instance.
  • Access modifiers — public, protected, package-private (default), private.
  • Record (Java 16+) — concise immutable data carrier.

A minimal class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Person {
private final String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() { return name; }
public int getAge() { return age; }
public void birthday() { this.age++; }

@Override
public String toString() {
return "Person(" + name + ", " + age + ")";
}
}

Use:

1
2
3
4
Person p = new Person("Ada", 30);
p.birthday();
System.out.println(p); // Person(Ada, 31)

static members

1
2
3
4
5
6
7
8
public class MathHelpers {
public static final double PI = 3.14159;
public static int square(int n) { return n * n; }
}

MathHelpers.PI; // access without instance
MathHelpers.square(5);

Records (Java 16+)

1
2
3
4
5
6
public record Point(double x, double y) {}

Point p = new Point(1.0, 2.0);
p.x(); // accessor auto-generated
System.out.println(p); // Point[x=1.0, y=2.0]

Records auto-generate constructor, accessors, equals, hashCode, toString. Use for plain data carriers.


Chapter 3 — Inheritance & Polymorphism

Terminology

  • extends — declares a subclass.
  • super — refers to the parent.
  • Override — replace a parent's method in a subclass.
  • Polymorphism — call a parent-typed reference, dispatch to the actual subclass.
  • final — prevents inheritance (on class) or overriding (on method).

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Animal {
protected String name;
public Animal(String name) { this.name = name; }
public String speak() { return name + " makes a sound."; }
}

public class Dog extends Animal {
public Dog(String name) { super(name); }
@Override
public String speak() { return name + " says woof!"; }
}

public class Cat extends Animal {
public Cat(String name) { super(name); }
@Override
public String speak() { return name + " says meow"; }
}

Animal[] zoo = { new Dog("Rex"), new Cat("Mochi") };
for (Animal a : zoo) {
System.out.println(a.speak()); // dynamic dispatch
}

Always use @Override — the compiler verifies you really did override (catches typos).

instanceof + pattern matching (modern)

1
2
3
4
if (a instanceof Dog d) {
d.fetch(); // d is auto-cast inside the block
}

Chapter 4 — Abstract Class, OOD Principles

Terminology

  • Abstract class — declared with abstract; cannot be instantiated; may contain abstract methods.
  • Interface — pure contract; can have default and static methods (Java 8+) and private helpers (Java 9+).
  • SOLID — five design principles (Single-responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion).

Abstract class

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Shape {
public abstract double area();
public String describe() {
return "A shape with area " + area();
}
}

public class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override public double area() { return Math.PI * r * r; }
}

Interface

1
2
3
4
5
6
7
8
9
10
11
public interface Drawable {
void draw(); // implicitly public abstract
default void blink() { // default method
for (int i = 0; i < 3; i++) draw();
}
}

public class Square implements Drawable {
@Override public void draw() { System.out.println("[ ]"); }
}

A class can extends only one class but implements many interfaces — the standard answer to "multiple inheritance" in Java.

OOD quick-reference (SOLID)

Principle Plain English
Single responsibility each class has one reason to change
Open / closed open for extension, closed for modification
Liskov substitution subtypes must work wherever the supertype works
Interface segregation many small interfaces beat one fat one
Dependency inversion depend on abstractions, not concretes

Also fundamental: composition over inheritance (prefer containing collaborators to subclassing them).


Chapter 5 — Generic Class

Terminology

  • Type parameter — <T>, <K, V>, etc.
  • Bounded type — <T extends Comparable<T>>.
  • Wildcards — ?, ? extends X, ? super X (PECS rule).

Generic class

1
2
3
4
5
6
7
8
9
10
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}

Box<String> bs = new Box<>("hello");
Box<Integer> bi = new Box<>(42);

Generic method

1
2
3
4
5
6
public static <T> List<T> repeat(T item, int n) {
List<T> out = new ArrayList<>();
for (int i = 0; i < n; i++) out.add(item);
return out;
}

Bounded + wildcards

1
2
3
4
5
6
7
8
9
10
11
public static <T extends Comparable<T>> T max(List<T> xs) {
T best = xs.get(0);
for (T x : xs) if (x.compareTo(best) > 0) best = x;
return best;
}

// PECS: Producer Extends, Consumer Super
void copy(List<? extends Number> from, List<? super Number> to) {
for (Number n : from) to.add(n);
}

Chapter 6 — Exception handling

Terminology

  • Throwable — root.
  • Error — JVM-level (OutOfMemoryError); usually don't catch.
  • Exception — recoverable; subclasses include the checked family (must be declared/handled) and RuntimeException (unchecked).
  • Checked exceptions — IOException, SQLException, InterruptedException, etc.
  • Unchecked — NullPointerException, IllegalArgumentException, IllegalStateException, etc.

Java exception family tree: Throwable splits into Error and Exception; Exception splits into checked exceptions and RuntimeException with its unchecked subclasses

try / catch / finally

1
2
3
4
5
6
7
8
9
10
11
12
try {
var data = Files.readString(Path.of("config.json"));
var cfg = parse(data);
} catch (IOException e) {
log.error("read failed", e);
throw new RuntimeException(e); // wrap and rethrow
} catch (ParseException e) {
log.error("bad config", e);
} finally {
cleanup();
}

try-with-resources (auto-close)

1
2
3
4
5
try (var in  = Files.newBufferedReader(Path.of("in.txt"));
var out = Files.newBufferedWriter(Path.of("out.txt"))) {
in.transferTo(out);
} // both closed even on exception

Implementing AutoCloseable lets your own classes participate.

Custom exceptions

1
2
3
4
5
6
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String msg) { super(msg); }
}

if (amount > balance) throw new InsufficientFundsException("balance too low");

Trick: prefer unchecked exceptions for programming errors (bug-style: bad arg, missing key). Reserve checked exceptions for genuinely recoverable I/O. Modern Java style leans towards fewer checked exceptions.


Chapter 7 — Assertion

assert checks a development-time invariant. Disabled by default in production unless run with -ea (enable assertions).

1
2
3
4
5
public int sqrt(int n) {
assert n >= 0 : "n must be non-negative, got " + n;
return (int) Math.sqrt(n);
}
1
2
3
java -ea Hello             # enable
java -da Hello # disable (default)

Rules:

  • Never use assert for argument validation in public APIs — use if + throw new IllegalArgumentException(...).
  • Use assert for invariants that should never be false if the code is correct.
  • Assertions are stripped at runtime by default — don't put side effects inside.

Chapter 8 — File I/O

The modern API is java.nio.file (NIO.2, Java 7+). Avoid the legacy java.io.File.

Read

1
2
3
4
5
6
7
8
9
Path p = Path.of("notes.txt");
String text = Files.readString(p);
List<String> lines = Files.readAllLines(p);

// large file — stream lines lazily
try (var stream = Files.lines(p)) {
stream.filter(s -> s.contains("ERROR")).forEach(System.out::println);
}

Write

1
2
3
Files.writeString(Path.of("out.txt"), "hello\nworld\n");
Files.write(Path.of("data.bin"), new byte[]{ 0x01, 0x02, 0x03 });

Path operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Path p = Path.of("data", "users", "ada.json");
p.toAbsolutePath();
p.getParent(); // data/users
p.getFileName(); // ada.json

Files.exists(p);
Files.createDirectories(p.getParent());
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
Files.delete(p);

// iterate
try (var stream = Files.walk(Path.of("."), 2)) {
stream.filter(Files::isRegularFile).forEach(System.out::println);
}

Streams + buffered

1
2
3
4
5
6
7
try (BufferedReader r = Files.newBufferedReader(Path.of("big.txt"))) {
String line;
while ((line = r.readLine()) != null) {
// process line
}
}

Chapter 9 — Multithreading

Terminology

  • Thread — a unit of execution scheduled by the OS.
  • Runnable — functional interface for a unit of work.
  • Thread-safe — correct when accessed from multiple threads simultaneously.
  • Race condition — outcome depends on thread interleaving.
  • synchronized — mutual-exclusion keyword.
  • volatile — guarantees visibility of writes across threads.
  • Virtual thread (Java 21+) — lightweight, JVM-scheduled; millions can run concurrently.

Threads the modern way

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// platform thread
Thread t = new Thread(() -> System.out.println("hi from thread"));
t.start();
t.join();

// virtual thread (Java 21+) — cheap, scalable
Thread vt = Thread.ofVirtual().start(() -> doWork());

// executor of virtual threads — preferred for servers
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
int id = i;
exec.submit(() -> handleRequest(id));
}
} // executor closes; waits for tasks

Virtual threads were the headline Java 21 feature. They make blocking I/O efficient at scale without rewriting code as async.

Synchronisation

1
2
3
4
5
6
public class Counter {
private int n = 0;
public synchronized void inc() { n++; }
public synchronized int get() { return n; }
}

Or with explicit locks for finer control:

1
2
3
4
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
try { /* critical section */ } finally { lock.unlock(); }

java.util.concurrent essentials

Type Use
AtomicInteger, AtomicLong, AtomicReference<T> lock-free counters / refs
ConcurrentHashMap thread-safe map
BlockingQueue<T> producer-consumer
CountDownLatch, CyclicBarrier, Semaphore coordination primitives
CompletableFuture<T> async pipelines
ExecutorService thread pool
ScheduledExecutorService periodic / delayed tasks

Trick: for most server workloads in 2026, Executors.newVirtualThreadPerTaskExecutor() + plain blocking code is the right answer. Reach for CompletableFuture only when you really need composition of async pipelines.


Practice Exercises

Exercise 1 — Class basics

Build a BankAccount class with deposit, withdraw (reject if overdraw), balance(). Override toString(). Add a transferTo(BankAccount other, double amount) method.

Exercise 2 — Inheritance

Define Shape (abstract double area()), Circle, Rectangle, Triangle. Put them in an array; print the total area.

Exercise 3 — Generics

Implement Stack<T> with push, pop, peek, isEmpty, size. Use an underlying ArrayList<T>.

Exercise 4 — Exception flow

Read a file path from args[0]; print its content. Catch NoSuchFileException and IOException separately, exit with different exit codes (2 and 3 respectively).

Solution sketch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Cat {
public static void main(String[] args) {
if (args.length != 1) { System.err.println("usage: cat <file>"); System.exit(1); }
try {
System.out.print(Files.readString(Path.of(args[0])));
} catch (NoSuchFileException e) {
System.err.println("not found: " + args[0]);
System.exit(2);
} catch (IOException e) {
System.err.println("io error: " + e.getMessage());
System.exit(3);
}
}
}

Exercise 5 — Concurrency

Spawn 1000 virtual threads, each downloading https://httpbin.org/uuid. Collect the responses into a List<String> (thread-safe!) and print the count of unique values.


Cheat-sheet

What How
Compile javac File.java
Run java MainClass
Single-file run (modern) java File.java
Class public class Name { ... }
Record public record Point(double x, double y) {}
Constructor public Name(...) { this.x = ...; }
Override @Override on method
Abstract class public abstract class X { abstract void f(); }
Interface public interface I { void f(); default void g() {} }
Generic class class Box<T> { T value; }
Bounded generic <T extends Comparable<T>>
Try with resources try (var r = ...) { ... }
Read file Files.readString(Path.of("..."))
Write file Files.writeString(Path.of("..."), text)
Walk dir Files.walk(Path.of("."))
Virtual thread Thread.ofVirtual().start(runnable)
Virtual executor Executors.newVirtualThreadPerTaskExecutor()
Sync method public synchronized void f()
Atomic int AtomicInteger
Thread-safe map ConcurrentHashMap

What's next

Part 2 covers GUI (JavaFX), events, functional programming, the Collections framework, and the Stream API.


References

  • docs.oracle.com/en/java/javase/21/ — official docs (LTS 21 in 2026).
  • Effective Java by Joshua Bloch — the canonical guide.
  • Java Concurrency in Practice by Brian Goetz — concurrency bible.
  • dev.java — Oracle's modern tutorial site.
  • Modern Java in Action by Urma / Fusco / Mycroft — covers lambdas, streams, modules.

Note: this post is in progress. Planned additions: build tools (Maven, Gradle), modules (JPMS), sealed classes, pattern matching for switch, structured concurrency.