Claude Code Plugins

Community-maintained marketplace

Feedback

lang-java-dev

@aRustyDev/ai
0
0

Foundational Java patterns covering core syntax, object-oriented programming, generics, collections, streams, lambdas, and modern Java features. Use when writing Java code, understanding the type system, working with collections/streams, or needing guidance on which specialized Java skill to use. This is the entry point for Java development.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name lang-java-dev
description Foundational Java patterns covering core syntax, object-oriented programming, generics, collections, streams, lambdas, and modern Java features. Use when writing Java code, understanding the type system, working with collections/streams, or needing guidance on which specialized Java skill to use. This is the entry point for Java development.

Java Fundamentals

Foundational Java patterns and core language features. This skill serves as both a reference for common patterns and an index to specialized Java skills.

Overview

┌─────────────────────────────────────────────────────────────────┐
│                      Java Skill Hierarchy                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌───────────────────┐                        │
│                    │   lang-java-dev   │ ◄── You are here       │
│                    │   (foundation)    │                        │
│                    └─────────┬─────────┘                        │
│                              │                                  │
│     ┌────────────┬───────────┼───────────┬────────────┐        │
│     │            │           │           │            │        │
│     ▼            ▼           ▼           ▼            ▼        │
│ ┌────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐   │
│ │patterns│ │  spring  │ │library │ │  build  │ │   test   │   │
│ │  -dev  │ │   -dev   │ │  -dev  │ │  -dev   │ │   -dev   │   │
│ └────────┘ └──────────┘ └────────┘ └─────────┘ └──────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

This skill covers:

  • Core syntax (classes, interfaces, enums, records)
  • Object-oriented programming (inheritance, polymorphism, encapsulation)
  • Generics fundamentals
  • Collections framework
  • Stream API and lambda expressions
  • Exception handling
  • Modern Java features (records, sealed classes, pattern matching)

This skill does NOT cover (see specialized skills):

  • Design patterns and best practices → lang-java-patterns-dev
  • Spring framework → lang-java-spring-dev
  • Library/package publishing → lang-java-library-dev
  • Build tools (Maven, Gradle) → lang-java-build-dev
  • Testing frameworks (JUnit, TestNG) → lang-java-test-dev

Quick Reference

Task Pattern
Create class class Name { }
Create interface interface Name { void method(); }
Create enum enum Status { PENDING, APPROVED }
Create record record Point(int x, int y) {}
Implement interface class Impl implements Interface { }
Extend class class Child extends Parent { }
Generic class class Box<T> { }
Lambda expression (x, y) -> x + y
Stream operation list.stream().filter(...).map(...)
Exception handling try { } catch (Exception e) { }

Skill Routing

Use this table to find the right specialized skill:

When you need to... Use this skill
Apply design patterns, SOLID principles lang-java-patterns-dev
Build Spring Boot applications lang-java-spring-dev
Publish JARs, Maven artifacts lang-java-library-dev
Configure Maven or Gradle lang-java-build-dev
Write unit tests, integration tests lang-java-test-dev

Core Types and Classes

Classes

// Basic class
public class User {
    // Instance variables (fields)
    private String name;
    private int age;

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

    // Getters and setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // Instance method
    public void greet() {
        System.out.println("Hello, I'm " + name);
    }

    // Static method
    public static User createDefault() {
        return new User("Anonymous", 0);
    }
}

// Usage
User user = new User("Alice", 30);
user.greet();

Interfaces

// Interface definition
public interface Drawable {
    // Abstract method (implicitly public abstract)
    void draw();

    // Default method (Java 8+)
    default void render() {
        System.out.println("Rendering...");
        draw();
    }

    // Static method (Java 8+)
    static void info() {
        System.out.println("Drawable interface");
    }

    // Private method (Java 9+) - helper for default methods
    private void log(String message) {
        System.out.println("LOG: " + message);
    }
}

// Implementation
public class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
}

// Multiple interfaces
public class Shape implements Drawable, Comparable<Shape> {
    @Override
    public void draw() { }

    @Override
    public int compareTo(Shape other) {
        return 0;
    }
}

Abstract Classes

public abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    // Abstract method - must be implemented by subclasses
    public abstract void makeSound();

    // Concrete method - inherited by subclasses
    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

Enums

// Simple enum
public enum Status {
    PENDING,
    APPROVED,
    REJECTED
}

// Enum with fields and methods
public enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6);

    private final double mass;
    private final double radius;

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    public double getMass() {
        return mass;
    }

    public double surfaceGravity() {
        return 6.67300E-11 * mass / (radius * radius);
    }
}

// Enum with abstract methods
public enum Operation {
    PLUS {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS {
        public double apply(double x, double y) { return x - y; }
    };

    public abstract double apply(double x, double y);
}

Records (Java 14+)

// Immutable data carrier
public record Point(int x, int y) {}

// Usage
Point p = new Point(10, 20);
System.out.println(p.x());  // Auto-generated accessor
System.out.println(p);       // Auto-generated toString()

// Record with custom methods
public record Person(String name, int age) {
    // Compact constructor for validation
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }

    // Additional methods
    public boolean isAdult() {
        return age >= 18;
    }
}

// Record with static factory
public record Range(int start, int end) {
    public static Range of(int start, int end) {
        if (start > end) {
            throw new IllegalArgumentException("Invalid range");
        }
        return new Range(start, end);
    }
}

Sealed Classes (Java 17+)

// Sealed class restricts which classes can extend it
public sealed class Shape
    permits Circle, Rectangle, Triangle {
}

public final class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }
}

public final class Rectangle extends Shape {
    private final double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
}

public non-sealed class Triangle extends Shape {
    // non-sealed allows further extension
}

Generics

Generic Classes

// Basic generic class
public class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

// Usage
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();

// Multiple type parameters
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

Generic Methods

// Generic method
public static <T> T getFirst(List<T> list) {
    if (list.isEmpty()) {
        return null;
    }
    return list.get(0);
}

// Usage - type inference
List<String> names = List.of("Alice", "Bob");
String first = getFirst(names);

// Multiple type parameters
public static <K, V> Pair<K, V> makePair(K key, V value) {
    return new Pair<>(key, value);
}

Bounded Type Parameters

// Upper bound (extends)
public static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

// Multiple bounds
public static <T extends Number & Comparable<T>> void process(T value) {
    // T must be a Number AND implement Comparable
}

// Lower bound (in wildcards)
public static void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    // Can add Integer or its subtypes
}

// Upper bound wildcard
public static double sum(List<? extends Number> list) {
    double sum = 0;
    for (Number num : list) {
        sum += num.doubleValue();
    }
    return sum;
}

Type Erasure and Raw Types

// Type information is erased at runtime
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();
// At runtime, both are just Box

// Cannot do:
// new T()  - type parameter instantiation
// T.class  - class literal
// instanceof T  - instance check

// Raw types (avoid - legacy compatibility only)
Box rawBox = new Box();  // Warning: unchecked

Collections Framework

List Interface

// ArrayList - dynamic array
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.get(0);  // "Apple"

// LinkedList - doubly-linked list
List<String> linkedList = new LinkedList<>();
linkedList.addFirst("First");
linkedList.addLast("Last");

// Immutable lists (Java 9+)
List<String> immutable = List.of("A", "B", "C");
// immutable.add("D");  // UnsupportedOperationException

// Common operations
list.size();
list.isEmpty();
list.contains("Apple");
list.remove("Banana");
list.clear();

Set Interface

// HashSet - unordered, no duplicates
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Apple");  // Ignored - duplicate
// Size is 2

// TreeSet - sorted, no duplicates
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(3);
// Iteration order: 1, 3, 5

// LinkedHashSet - insertion order, no duplicates
Set<String> linkedSet = new LinkedHashSet<>();

// Immutable sets (Java 9+)
Set<String> immutable = Set.of("A", "B", "C");

Map Interface

// HashMap - unordered key-value pairs
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Alice", 30);
hashMap.put("Bob", 25);
int age = hashMap.get("Alice");  // 30

// TreeMap - sorted by keys
Map<String, Integer> treeMap = new TreeMap<>();

// LinkedHashMap - insertion order
Map<String, Integer> linkedMap = new LinkedHashMap<>();

// Immutable maps (Java 9+)
Map<String, Integer> immutable = Map.of(
    "Alice", 30,
    "Bob", 25
);

// Common operations
map.containsKey("Alice");
map.containsValue(30);
map.remove("Bob");
map.getOrDefault("Charlie", 0);
map.putIfAbsent("David", 35);

// Iteration
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
}

// Java 8+ forEach
map.forEach((key, value) ->
    System.out.println(key + ": " + value)
);

Queue and Deque

// Queue - FIFO
Queue<String> queue = new LinkedList<>();
queue.offer("First");   // Add to end
queue.offer("Second");
String head = queue.poll();  // Remove from front
String peek = queue.peek();  // View front without removing

// Deque - double-ended queue
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("Front");
deque.addLast("Back");
String first = deque.removeFirst();
String last = deque.removeLast();

// Stack operations (use Deque instead of Stack class)
Deque<String> stack = new ArrayDeque<>();
stack.push("Bottom");
stack.push("Top");
String top = stack.pop();

Stream API

Creating Streams

// From collection
List<String> list = List.of("A", "B", "C");
Stream<String> stream = list.stream();

// From array
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);

// Using Stream.of
Stream<String> stream = Stream.of("A", "B", "C");

// Infinite streams
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
Stream<Double> random = Stream.generate(Math::random);

// Range
IntStream.range(1, 10);        // 1 to 9
IntStream.rangeClosed(1, 10);  // 1 to 10

Intermediate Operations

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

// filter - keep matching elements
List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

// map - transform elements
List<Integer> lengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());

// flatMap - flatten nested structures
List<List<String>> nested = List.of(
    List.of("A", "B"),
    List.of("C", "D")
);
List<String> flat = nested.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());

// distinct - remove duplicates
List<Integer> unique = Stream.of(1, 2, 2, 3, 3, 3)
    .distinct()
    .collect(Collectors.toList());

// sorted - sort elements
List<String> sorted = names.stream()
    .sorted()
    .collect(Collectors.toList());

// sorted with comparator
List<String> sortedByLength = names.stream()
    .sorted(Comparator.comparing(String::length))
    .collect(Collectors.toList());

// limit - take first n elements
List<String> limited = names.stream()
    .limit(2)
    .collect(Collectors.toList());

// skip - skip first n elements
List<String> skipped = names.stream()
    .skip(2)
    .collect(Collectors.toList());

// peek - perform action without modifying stream
names.stream()
    .peek(System.out::println)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

Terminal Operations

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// collect - gather into collection
List<Integer> list = numbers.stream().collect(Collectors.toList());
Set<Integer> set = numbers.stream().collect(Collectors.toSet());

// forEach - perform action on each element
numbers.stream().forEach(System.out::println);

// reduce - combine elements
int sum = numbers.stream().reduce(0, Integer::sum);
Optional<Integer> max = numbers.stream().reduce(Integer::max);

// count - count elements
long count = numbers.stream().count();

// anyMatch, allMatch, noneMatch
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean noNegative = numbers.stream().noneMatch(n -> n < 0);

// findFirst, findAny
Optional<Integer> first = numbers.stream().findFirst();
Optional<Integer> any = numbers.stream().findAny();

// min, max
Optional<Integer> min = numbers.stream().min(Integer::compare);
Optional<Integer> max = numbers.stream().max(Integer::compare);

Collectors

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

// toList, toSet
List<String> list = names.stream().collect(Collectors.toList());
Set<String> set = names.stream().collect(Collectors.toSet());

// toMap
Map<String, Integer> nameToLength = names.stream()
    .collect(Collectors.toMap(
        name -> name,
        String::length
    ));

// groupingBy
Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));

// partitioningBy
Map<Boolean, List<String>> partition = names.stream()
    .collect(Collectors.partitioningBy(name -> name.length() > 3));

// joining
String joined = names.stream()
    .collect(Collectors.joining(", "));

// summarizing
IntSummaryStatistics stats = names.stream()
    .collect(Collectors.summarizingInt(String::length));

Lambda Expressions and Method References

Lambda Syntax

// No parameters
Runnable r = () -> System.out.println("Hello");

// One parameter (parentheses optional)
Consumer<String> c = s -> System.out.println(s);
Consumer<String> c2 = (s) -> System.out.println(s);

// Multiple parameters
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// Block body
Predicate<String> isEmpty = s -> {
    return s.isEmpty();
};

// Type declarations (optional)
BiFunction<Integer, Integer, Integer> add2 =
    (Integer a, Integer b) -> a + b;

Functional Interfaces

// Built-in functional interfaces

// Predicate<T> - boolean test
Predicate<String> isEmpty = String::isEmpty;
Predicate<Integer> isEven = n -> n % 2 == 0;

// Function<T, R> - transform T to R
Function<String, Integer> length = String::length;
Function<Integer, String> toString = Object::toString;

// Consumer<T> - consume T, return nothing
Consumer<String> print = System.out::println;

// Supplier<T> - supply T, no input
Supplier<String> supplier = () -> "Hello";

// BiFunction<T, U, R> - two inputs
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// BiConsumer<T, U> - two inputs, no output
BiConsumer<String, Integer> printWithCount =
    (s, n) -> System.out.println(s + ": " + n);

// UnaryOperator<T> - T -> T
UnaryOperator<Integer> square = n -> n * n;

// BinaryOperator<T> - (T, T) -> T
BinaryOperator<Integer> max = Integer::max;

Method References

// Static method reference
Function<String, Integer> parseInt = Integer::parseInt;

// Instance method reference (bound)
String str = "Hello";
Supplier<Integer> length = str::length;

// Instance method reference (unbound)
Function<String, Integer> length2 = String::length;

// Constructor reference
Supplier<List<String>> listSupplier = ArrayList::new;
Function<String, User> userFactory = User::new;

// Array constructor reference
IntFunction<int[]> arrayMaker = int[]::new;

Exception Handling

Try-Catch-Finally

// Basic try-catch
try {
    int result = divide(10, 0);
} catch (ArithmeticException e) {
    System.err.println("Cannot divide by zero: " + e.getMessage());
}

// Multiple catch blocks
try {
    String text = readFile("data.txt");
    int number = Integer.parseInt(text);
} catch (IOException e) {
    System.err.println("File error: " + e.getMessage());
} catch (NumberFormatException e) {
    System.err.println("Parse error: " + e.getMessage());
}

// Multi-catch (Java 7+)
try {
    // risky operation
} catch (IOException | SQLException e) {
    System.err.println("Error: " + e.getMessage());
}

// Finally block - always executes
try {
    openConnection();
    processData();
} catch (Exception e) {
    handleError(e);
} finally {
    closeConnection();  // Always runs
}

Try-with-Resources

// Automatic resource management (Java 7+)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    // reader is automatically closed
} catch (IOException e) {
    e.printStackTrace();
}

// Multiple resources
try (FileInputStream in = new FileInputStream("input.txt");
     FileOutputStream out = new FileOutputStream("output.txt")) {
    // Both streams automatically closed
} catch (IOException e) {
    e.printStackTrace();
}

// Java 9+ - resources declared outside
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
    // Use reader
}

Checked vs Unchecked Exceptions

// Checked exceptions - must be handled or declared
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // Must declare throws or wrap in try-catch
}

// Unchecked exceptions - optional handling
public void divide(int a, int b) {
    int result = a / b;  // May throw ArithmeticException
    // No need to declare or catch
}

// Common checked exceptions:
// - IOException
// - SQLException
// - ClassNotFoundException

// Common unchecked exceptions:
// - NullPointerException
// - IllegalArgumentException
// - IllegalStateException
// - IndexOutOfBoundsException

Custom Exceptions

// Custom checked exception
public class ValidationException extends Exception {
    public ValidationException(String message) {
        super(message);
    }

    public ValidationException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Custom unchecked exception
public class InvalidConfigException extends RuntimeException {
    public InvalidConfigException(String message) {
        super(message);
    }
}

// Usage
public void validate(String input) throws ValidationException {
    if (input == null || input.isEmpty()) {
        throw new ValidationException("Input cannot be empty");
    }
}

Pattern Matching (Java 16+)

Pattern Matching for instanceof

// Old way
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
}

// Pattern matching (Java 16+)
if (obj instanceof String str) {
    System.out.println(str.length());
}

// With negation
if (!(obj instanceof String str)) {
    return;
}
System.out.println(str.length());

// In complex conditions
if (obj instanceof String str && str.length() > 5) {
    System.out.println("Long string: " + str);
}

Switch Expressions (Java 14+)

// Traditional switch
int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    default:
        numLetters = 0;
}

// Switch expression
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
};

// With yield for blocks
String result = switch (day) {
    case MONDAY, TUESDAY -> {
        System.out.println("Weekday");
        yield "Work";
    }
    case SATURDAY, SUNDAY -> {
        System.out.println("Weekend");
        yield "Rest";
    }
    default -> "Unknown";
};

Pattern Matching for Switch (Java 21+)

// Type patterns in switch
static String format(Object obj) {
    return switch (obj) {
        case Integer i -> "Integer: " + i;
        case String s -> "String: " + s;
        case null -> "null";
        default -> "Unknown";
    };
}

// With guards
static String describe(Object obj) {
    return switch (obj) {
        case String s when s.length() > 10 -> "Long string";
        case String s -> "Short string: " + s;
        case Integer i when i > 100 -> "Large number";
        case Integer i -> "Small number: " + i;
        default -> "Other";
    };
}

// Record patterns
record Point(int x, int y) {}

static void printPoint(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println("Point at " + x + ", " + y);
    }
}

Common Patterns

Builder Pattern

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

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
    }

    public static class Builder {
        private String name;
        private String email;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// Usage
User user = new User.Builder()
    .name("Alice")
    .email("alice@example.com")
    .age(30)
    .build();

Optional Pattern

// Creating Optional
Optional<String> optional = Optional.of("value");
Optional<String> nullable = Optional.ofNullable(getValue());
Optional<String> empty = Optional.empty();

// Using Optional
optional.ifPresent(System.out::println);

String value = optional.orElse("default");
String value2 = optional.orElseGet(() -> getDefault());
String value3 = optional.orElseThrow(() -> new IllegalStateException());

// Transforming
Optional<Integer> length = optional.map(String::length);
Optional<String> upper = optional
    .filter(s -> s.length() > 5)
    .map(String::toUpperCase);

// Java 9+ - or(), ifPresentOrElse(), stream()
Optional<String> result = optional.or(() -> getAlternative());

optional.ifPresentOrElse(
    System.out::println,
    () -> System.out.println("Empty")
);

Factory Methods

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

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

    // Factory method
    public static User create(String name, int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
        return new User(name, age);
    }

    // Named factory methods
    public static User createChild(String name) {
        return new User(name, 0);
    }

    public static User createAdult(String name, int age) {
        if (age < 18) {
            throw new IllegalArgumentException("Must be 18 or older");
        }
        return new User(name, age);
    }
}

Troubleshooting

NullPointerException

Problem: Accessing methods/fields on null references

String str = null;
int length = str.length();  // NullPointerException

Fix: Use null checks or Optional

// Null check
if (str != null) {
    int length = str.length();
}

// Optional
Optional<String> opt = Optional.ofNullable(str);
opt.ifPresent(s -> System.out.println(s.length()));

ClassCastException

Problem: Invalid type casting

Object obj = "Hello";
Integer num = (Integer) obj;  // ClassCastException

Fix: Use instanceof before casting

if (obj instanceof Integer) {
    Integer num = (Integer) obj;
}

// Or pattern matching (Java 16+)
if (obj instanceof Integer num) {
    System.out.println(num);
}

ConcurrentModificationException

Problem: Modifying collection while iterating

List<String> list = new ArrayList<>(List.of("A", "B", "C"));
for (String item : list) {
    list.remove(item);  // ConcurrentModificationException
}

Fix: Use iterator's remove or collect to new list

// Use iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    it.remove();
}

// Or create new list
List<String> filtered = list.stream()
    .filter(item -> !item.equals("B"))
    .collect(Collectors.toList());

Generic Type Erasure Issues

Problem: Cannot create generic array or use instanceof with generics

// T[] array = new T[10];  // Error
// if (obj instanceof List<String>) {}  // Error

Fix: Use List instead of array, or unchecked cast with warning

// Use List
List<T> list = new ArrayList<>();

// Or use Array.newInstance
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(componentType, size);

Module System

Java uses packages for namespace organization and modules (JPMS, Java 9+) for stronger encapsulation.

Packages

// Package declaration (must be first statement)
package com.example.myapp.util;

// Import statements
import java.util.List;
import java.util.ArrayList;
import java.util.stream.*;  // Wildcard import (all classes)
import static java.lang.Math.PI;  // Static import
import static java.util.Collections.*;  // Static wildcard

public class StringUtils {
    public static String capitalize(String s) {
        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
    }
}

Package Naming Conventions

com.company.project.module.submodule

Examples:
com.example.myapp           # Application root
com.example.myapp.model     # Domain models
com.example.myapp.service   # Business logic
com.example.myapp.controller # Web controllers
com.example.myapp.repository # Data access
com.example.myapp.util      # Utilities

Access Modifiers

Modifier Class Package Subclass World
public
protected
(default)
private

Java Modules (JPMS, Java 9+)

// module-info.java (at source root)
module com.example.myapp {
    // Required modules
    requires java.base;  // Implicit
    requires java.sql;
    requires transitive java.logging;  // Transitive dependency

    // Exported packages (public API)
    exports com.example.myapp.api;
    exports com.example.myapp.model;

    // Qualified exports (to specific modules)
    exports com.example.myapp.internal to com.example.tests;

    // Open for reflection
    opens com.example.myapp.model to com.google.gson;

    // Service provider
    provides com.example.spi.Plugin
        with com.example.myapp.MyPlugin;

    // Service consumer
    uses com.example.spi.Plugin;
}

Project Structure

myproject/
├── src/
│   └── main/
│       ├── java/
│       │   ├── module-info.java
│       │   └── com/
│       │       └── example/
│       │           └── myapp/
│       │               ├── Main.java
│       │               ├── api/
│       │               │   └── UserService.java
│       │               └── model/
│       │                   └── User.java
│       └── resources/
│           └── application.properties
└── pom.xml (or build.gradle)

Concurrency

Java provides multiple concurrency mechanisms from low-level threads to high-level abstractions.

Threads

// Extending Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Running in thread");
    }
}

// Implementing Runnable (preferred)
class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Task running");
    }
}

// Lambda (Java 8+)
Thread thread = new Thread(() -> System.out.println("Lambda thread"));

// Starting threads
new MyThread().start();
new Thread(new MyTask()).start();
thread.start();

// Waiting for completion
thread.join();

Synchronized

public class Counter {
    private int count = 0;

    // Synchronized method
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

// Synchronized block
public class SafeList {
    private final List<String> list = new ArrayList<>();
    private final Object lock = new Object();

    public void add(String item) {
        synchronized (lock) {
            list.add(item);
        }
    }
}

ExecutorService

import java.util.concurrent.*;

// Fixed thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);

// Submit tasks
executor.execute(() -> System.out.println("Simple task"));

Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Result";
});

// Get result (blocks)
String result = future.get();
String resultWithTimeout = future.get(5, TimeUnit.SECONDS);

// Shutdown
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);

CompletableFuture (Java 8+)

import java.util.concurrent.CompletableFuture;

// Create async tasks
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return fetchData();
});

// Chain operations
CompletableFuture<Integer> result = future
    .thenApply(data -> parse(data))
    .thenApply(parsed -> process(parsed))
    .exceptionally(ex -> handleError(ex));

// Combine futures
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> combined = future1.thenCombine(future2,
    (a, b) -> a + " " + b);

// Wait for all
CompletableFuture.allOf(future1, future2).join();

// Wait for any
CompletableFuture.anyOf(future1, future2).join();

Concurrent Collections

import java.util.concurrent.*;

// Thread-safe collections
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.computeIfAbsent("key", k -> expensiveComputation(k));

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
BlockingQueue<String> queue = new LinkedBlockingQueue<>();

// Atomic types
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(5, 10);

Virtual Threads (Java 21+)

// Virtual threads - lightweight, cheap to create
Thread.startVirtualThread(() -> {
    System.out.println("Virtual thread running");
});

// With executor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> {
            // Each task runs on a virtual thread
            return fetchData();
        });
    }
}

See also: patterns-concurrency-dev for cross-language concurrency patterns


Metaprogramming

Java uses annotations and reflection for metaprogramming.

Annotations

// Built-in annotations
@Override
public String toString() { ... }

@Deprecated
public void oldMethod() { ... }

@SuppressWarnings("unchecked")
public List<T> getList() { ... }

@FunctionalInterface
interface Processor<T> {
    T process(T input);
}

Custom Annotations

import java.lang.annotation.*;

// Runtime annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
    String value() default "";
    Level level() default Level.INFO;
}

// Compile-time annotation
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Generated {
    String date();
    String author();
}

// Usage
public class MyService {
    @Loggable(level = Level.DEBUG)
    public void doWork() {
        // ...
    }
}

Reflection

import java.lang.reflect.*;

// Get class information
Class<?> clazz = User.class;
Class<?> clazz2 = obj.getClass();
Class<?> clazz3 = Class.forName("com.example.User");

// Inspect fields
for (Field field : clazz.getDeclaredFields()) {
    System.out.println(field.getName() + ": " + field.getType());
}

// Inspect methods
for (Method method : clazz.getDeclaredMethods()) {
    System.out.println(method.getName());
    Loggable annotation = method.getAnnotation(Loggable.class);
    if (annotation != null) {
        System.out.println("Loggable: " + annotation.value());
    }
}

// Create instance
Object instance = clazz.getDeclaredConstructor().newInstance();

// Access private fields
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(instance, "Alice");

// Invoke methods
Method method = clazz.getDeclaredMethod("greet", String.class);
Object result = method.invoke(instance, "World");

Annotation Processing

// Annotation processor (runs at compile time)
@SupportedAnnotationTypes("com.example.Generated")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class GeneratedProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                          RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(Generated.class)) {
            // Generate code
        }
        return true;
    }
}

See also: patterns-metaprogramming-dev for cross-language comparison


Serialization

Java supports multiple serialization frameworks, with Jackson being the most popular for JSON.

Jackson Basics

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

// Serialize
User user = new User("Alice", 30);
String json = mapper.writeValueAsString(user);

// Pretty print
String prettyJson = mapper.writerWithDefaultPrettyPrinter()
    .writeValueAsString(user);

// Deserialize
User parsed = mapper.readValue(json, User.class);

// Collections
List<User> users = mapper.readValue(jsonArray,
    new TypeReference<List<User>>() {});

Jackson Annotations

import com.fasterxml.jackson.annotation.*;

public class User {
    @JsonProperty("user_id")
    private String id;

    private String name;

    @JsonIgnore
    private String password;

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    private LocalDateTime createdAt;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String email;

    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthDate;
}

// Ignore unknown properties
@JsonIgnoreProperties(ignoreUnknown = true)
public class Config { ... }

// Polymorphic types
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal { ... }

Bean Validation (Jakarta Validation)

import jakarta.validation.constraints.*;

public class User {
    @NotNull
    @Size(min = 1, max = 100)
    private String name;

    @NotBlank
    @Email
    private String email;

    @Min(0)
    @Max(150)
    private int age;

    @Pattern(regexp = "^[A-Z]{2}\\d{6}$")
    private String licenseNumber;

    @NotEmpty
    private List<@NotBlank String> roles;
}

// Validation
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

Set<ConstraintViolation<User>> violations = validator.validate(user);
for (ConstraintViolation<User> v : violations) {
    System.out.println(v.getPropertyPath() + ": " + v.getMessage());
}

Custom Jackson Serializer

public class MoneySerializer extends JsonSerializer<Money> {
    @Override
    public void serialize(Money value, JsonGenerator gen,
                         SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeNumberField("amount", value.getAmount());
        gen.writeStringField("currency", value.getCurrency());
        gen.writeEndObject();
    }
}

// Usage
@JsonSerialize(using = MoneySerializer.class)
private Money price;

See also: patterns-serialization-dev for cross-language serialization patterns


Build and Dependencies

Java uses Maven or Gradle for build and dependency management.

Maven (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

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

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
        </plugins>
    </build>
</project>

Gradle (build.gradle.kts)

plugins {
    java
    application
}

group = "com.example"
version = "1.0.0-SNAPSHOT"

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

application {
    mainClass.set("com.example.Main")
}

tasks.test {
    useJUnitPlatform()
}

Common Commands

Maven Gradle Purpose
mvn compile gradle build Compile
mvn test gradle test Run tests
mvn package gradle jar Create JAR
mvn install gradle publishToMavenLocal Install locally
mvn clean gradle clean Clean build
mvn dependency:tree gradle dependencies Show deps

Dependency Scopes

Scope Compile Test Runtime
compile (default)
provided
runtime
test

Testing

Java uses JUnit for testing, commonly with Mockito for mocking.

JUnit 5 Basics

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void shouldAddTwoNumbers() {
        int result = calculator.add(2, 3);
        assertEquals(5, result);
    }

    @Test
    void shouldThrowOnDivisionByZero() {
        assertThrows(ArithmeticException.class, () -> {
            calculator.divide(10, 0);
        });
    }

    @Test
    @DisplayName("Addition is commutative")
    void additionIsCommutative() {
        assertEquals(
            calculator.add(2, 3),
            calculator.add(3, 2)
        );
    }
}

Assertions

// Equality
assertEquals(expected, actual);
assertEquals(expected, actual, "Custom message");
assertNotEquals(unexpected, actual);

// Boolean
assertTrue(condition);
assertFalse(condition);

// Null checks
assertNull(value);
assertNotNull(value);

// Same instance
assertSame(expected, actual);
assertNotSame(unexpected, actual);

// Arrays
assertArrayEquals(expected, actual);

// Exceptions
assertThrows(IllegalArgumentException.class, () -> {
    service.process(null);
});

// Timeout
assertTimeout(Duration.ofSeconds(5), () -> {
    slowOperation();
});

// Multiple assertions
assertAll(
    () -> assertEquals("Alice", user.getName()),
    () -> assertEquals(30, user.getAge()),
    () -> assertNotNull(user.getEmail())
);

Parameterized Tests

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void shouldBePositive(int number) {
    assertTrue(number > 0);
}

@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "10, 20, 30",
    "-1, 1, 0"
})
void shouldAdd(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

@ParameterizedTest
@MethodSource("numberProvider")
void shouldBeEven(int number) {
    assertEquals(0, number % 2);
}

static Stream<Integer> numberProvider() {
    return Stream.of(2, 4, 6, 8);
}

Mockito

import org.mockito.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository repository;

    @InjectMocks
    private UserService service;

    @Test
    void shouldFindUserById() {
        // Given
        User user = new User(1L, "Alice");
        when(repository.findById(1L)).thenReturn(Optional.of(user));

        // When
        User result = service.getUser(1L);

        // Then
        assertEquals("Alice", result.getName());
        verify(repository).findById(1L);
    }

    @Test
    void shouldThrowWhenUserNotFound() {
        when(repository.findById(anyLong())).thenReturn(Optional.empty());

        assertThrows(UserNotFoundException.class, () -> {
            service.getUser(999L);
        });
    }
}

Test Lifecycle

class LifecycleTest {
    @BeforeAll
    static void setUpClass() {
        // Run once before all tests
    }

    @AfterAll
    static void tearDownClass() {
        // Run once after all tests
    }

    @BeforeEach
    void setUp() {
        // Run before each test
    }

    @AfterEach
    void tearDown() {
        // Run after each test
    }

    @Test
    @Disabled("Not implemented yet")
    void pendingTest() {
        // Skipped
    }
}

Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev - Threads, executors, async patterns
  • patterns-serialization-dev - Jackson, validation, annotations
  • patterns-metaprogramming-dev - Annotations, reflection, processors

References