1.2 Java Intro For Experienced Devs

If you're coming from another language like Python, JavaScript, C#, Go, or Rust, this section gives you a fast-track overview of Java's core characteristics and why it remains a compelling choice in 2025.

Why Java in 2026?

  • Mature Ecosystem Java has been evolving for 30 years. The JVM, standard library, and tooling are battle-tested at scale. Millions of production systems depend on Java, from microservices to large enterprise platforms.

  • Performance and Predictability The JVM's JIT compiler (C2, Graal) delivers near-native performance. Garbage collectors (G1, ZGC, Shenandoah) provide sub-millisecond pause times. Unlike interpreted languages, Java excels at sustained throughput under heavy load.

  • Type Safety Without Ceremony Java is statically typed with modern type inference (var), pattern matching, and sealed types. You get compile-time safety without excessive boilerplate. Records eliminate data class verbosity.

  • Concurrency Made Simple Virtual threads (Project Loom) make high-concurrency code readable and efficient. Write synchronous-looking code that scales to millions of concurrent operations without callback hell or complex async runtimes.

  • World-Class Tooling IDEs (IntelliJ IDEA, VS Code with extensions, Eclipse), build tools (Maven, Gradle), profilers (JFR, async-profiler), and monitoring (JMX, Micrometer) are mature and well-integrated.

  • Six-Month Release Cadence Since JDK 9, Oracle ships a new feature release every six months. LTS versions (like 21, next is 25 in some editions) provide long-term support. You can adopt features incrementally without waiting years.

Java Language Essentials

Type System

Java is statically typed with nominal subtyping. Every value has a declared type known at compile time.

String message = "Hello";       // explicit type
var count = 42;                  // type inference (int)
List<String> names = List.of("Alice", "Bob");

Generics provide compile-time type safety for collections and algorithms:

List<Integer> numbers = new ArrayList<>();
numbers.add(42);
// numbers.add("text"); // compile error

Type Inference with var (Java 10+) reduces verbosity without losing safety:

var builder = new StringBuilder();  // type is StringBuilder
var map = Map.of("key", "value");   // Map<String, String>

Object-Oriented Core

Everything is a class or interface (except primitives: int, long, double, boolean, etc.).

Classes define state and behavior:

public class User {
    private String name;
    private int age;

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

    public String getName() { return name; }
    public int getAge() { return age; }
}

Records (Java 16+) eliminate boilerplate for immutable data:

public record User(String name, int age) {}
// Auto-generates constructor, getters, equals(), hashCode(), toString()

Interfaces define contracts; classes implement them:

public interface Logger {
    void log(String message);
}

public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

Null Safety

Java doesn't have Rust's Option or Kotlin's ? at the language level, but:

  • Optional<T> wraps nullable values explicitly
  • Modern APIs prefer returning empty collections over null
  • Sealed types + pattern matching provide exhaustive null handling
Optional<String> maybeName = findUser(id);
String name = maybeName.orElse("Unknown");

// Pattern matching (JDK 21+)
if (obj instanceof String s) {
    System.out.println(s.toUpperCase());
}

Exception Handling

Java uses checked exceptions (must declare/catch) and unchecked exceptions (runtime errors):

public void readFile(String path) throws IOException {
    Files.readString(Path.of(path));
}

try {
    readFile("config.txt");
} catch (IOException e) {
    System.err.println("Read failed: " + e.getMessage());
}

Modern Java code prefers:

  • Unchecked exceptions for programming errors (IllegalArgumentException, NullPointerException)
  • Checked exceptions for recoverable failures (I/O, network)
  • try-with-resources for automatic resource cleanup

Immutability by Default

Modern Java favors immutable data:

  • final fields (cannot be reassigned)
  • Immutable collections: List.of(), Set.of(), Map.of()
  • Records are implicitly immutable
public record Point(double x, double y) {}

var origin = new Point(0, 0);
// origin.x = 5; // compile error: no setter

Functional Programming Support

Java has first-class functions via lambda expressions and method references:

List<String> names = List.of("Alice", "Bob", "Charlie");

// Lambda
names.stream()
     .filter(name -> name.startsWith("A"))
     .forEach(System.out::println);  // method reference

// Function composition
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addExclaim = s -> s + "!";
Function<String, String> combined = toUpper.andThen(addExclaim);

System.out.println(combined.apply("hello")); // HELLO!

Stream API enables declarative data processing:

var total = orders.stream()
    .filter(order -> order.status() == Status.PAID)
    .mapToInt(Order::amount)
    .sum();

Package and Module System

Packages organize classes into namespaces:

package com.example.app;

import java.util.List;
import java.time.LocalDate;

Modules (JPMS) (Java 9+) provide stronger encapsulation:

// module-info.java
module com.example.app {
    requires java.net.http;
    exports com.example.app.api;
}

Modules control which packages are visible to other modules, preventing accidental dependencies on internal APIs.

Build and Dependency Management

Maven and Gradle are the standard build tools for Java projects.

Maven uses XML configuration:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.16.0</version>
</dependency>

Gradle uses Groovy or Kotlin DSL:

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
}

Both tools automatically resolve transitive dependencies, manage separate compile and test classpaths, and produce deployable artifacts (JARs, WARs, or executables).

Comparison with Other Languages

Feature Java Python JavaScript Go
Typing Static, nominal Dynamic Dynamic Static
Memory GC (automatic) GC GC GC
Concurrency Virtual threads, parallel streams asyncio, threads async/await, workers goroutines
Performance Near-native (JIT) Interpreted JIT (V8) Compiled
Ecosystem Maven Central (millions) PyPI npm Go modules
Deployment JAR, native image (GraalVM) Scripts, wheels Node.js, bundlers Single binary

When to Choose Java:

  • You need strong typing with good inference
  • Performance and memory efficiency matter
  • You're building long-lived services or platforms
  • You want mature tooling and a huge library ecosystem
  • You need enterprise-grade monitoring and profiling

Next Steps

Now that you understand Java's fundamentals, let's set up a modern development environment.

The next section walks through installing the JDK, configuring VS Code, and creating your first project with Maven/Gradle.