Java is a statically typed, object-oriented language that runs on the JVM. This cheat sheet covers Java 17+ — from primitives and OOP to Streams, Records, and concurrency — with copy-ready examples for everyday development.
Quick reference
The 25 patterns that cover 95% of everyday Java development.
| Pattern | Example |
|---|---|
| Variable | int x = 42; |
| String | String s = "hello"; |
| Array | int[] arr = {1, 2, 3}; |
| ArrayList | List<String> list = new ArrayList<>(); |
| HashMap | Map<String, Integer> map = new HashMap<>(); |
| For-each | for (String item : list) { } |
| Stream filter | list.stream().filter(s -> s.length() > 3) |
| Stream map | .map(String::toUpperCase).toList() |
| Optional | Optional.ofNullable(value).orElse("default") |
| Lambda | Runnable r = () -> System.out.println("hi"); |
| Method reference | list.forEach(System.out::println) |
| Record | record Point(int x, int y) {} |
| Switch expression | int val = switch (day) { case MON -> 1; ... }; |
| Try-with-resources | try (var in = new FileInputStream(f)) { } |
| String format | "Hello %s, you are %d".formatted(name, age) |
| String join | String.join(", ", list) |
| var (local inference) | var items = new ArrayList<String>(); |
| instanceof pattern | if (obj instanceof String s) { use(s); } |
| Sealed class | sealed interface Shape permits Circle, Rect {} |
| Text block | `String json = """ {"key":"val"} """;` |
| Comparator | list.sort(Comparator.comparing(Person::name)) |
| Collectors | .collect(Collectors.groupingBy(String::length)) |
| CompletableFuture | CompletableFuture.supplyAsync(() -> fetch()) |
| Synchronized | synchronized (lock) { /* critical section */ } |
| Interface default | default void greet() { System.out.println("hi"); } |
Variables and types
// Primitives
int count = 42;
long big = 10_000_000L;
double price = 9.99;
boolean active = true;
char ch = 'A';
// Reference types
String name = "Alice";
Integer boxed = 100; // auto-boxing from int
// Local type inference (Java 10+)
var list = new ArrayList<String>();
var map = new HashMap<String, Integer>();
// Constants
final int MAX_SIZE = 100;
static final double PI = 3.14159;
// Type casting
double d = 3.7;
int i = (int) d; // 3 — truncates, not rounds
// String to number
int parsed = Integer.parseInt("42");
double parsedD = Double.parseDouble("3.14");
// Number to string
String s = String.valueOf(42);
String s2 = Integer.toString(42);
Strings
String s = "Hello, World!";
// Common methods
s.length() // 13
s.toUpperCase() // "HELLO, WORLD!"
s.toLowerCase() // "hello, world!"
s.trim() // strips leading/trailing whitespace
s.strip() // Unicode-aware (Java 11+)
s.isEmpty() // false
s.isBlank() // false (Java 11+)
s.contains("World") // true
s.startsWith("Hello") // true
s.endsWith("!") // true
s.indexOf("o") // 4
s.lastIndexOf("o") // 8
s.substring(7, 12) // "World"
s.replace("World", "Java") // "Hello, Java!"
s.replaceAll("\\d+", "#") // regex replace
s.split(", ") // ["Hello", "World!"]
s.charAt(0) // 'H'
String.join("-", "a", "b") // "a-b"
// String.format / formatted (Java 15+)
String msg = "Name: %s, Age: %d, Score: %.2f".formatted("Alice", 30, 9.5);
// Text blocks (Java 15+)
String json = """
{
"name": "Alice",
"age": 30
}
""";
// StringBuilder for concatenation in loops
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item).append(", ");
}
String result = sb.toString();
// Comparison — NEVER use == for strings
"hello".equals(s) // correct
"hello".equalsIgnoreCase(s) // case-insensitive
s.compareTo("hello") // lexicographic
Arrays
// Declaration and initialization
int[] nums = {1, 2, 3, 4, 5};
String[] arr = new String[3]; // null, null, null
// Access
int first = nums[0];
int last = nums[nums.length - 1];
// Iterate
for (int n : nums) System.out.println(n);
// Sort and search
Arrays.sort(nums);
int idx = Arrays.binarySearch(nums, 3); // requires sorted
// Copy
int[] copy = Arrays.copyOf(nums, nums.length);
int[] slice = Arrays.copyOfRange(nums, 1, 4); // [2, 3, 4]
// 2D array
int[][] matrix = new int[3][3];
matrix[0][0] = 1;
// Array to string (for printing)
System.out.println(Arrays.toString(nums)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.deepToString(matrix)); // 2D arrays
Collections
List
// ArrayList — indexed, allows duplicates
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.add("d");
list.add(0, "z"); // insert at index
list.remove("b"); // by value
list.remove(0); // by index
list.get(1); // "b"
list.size(); // 3
list.contains("c"); // true
list.indexOf("c"); // 1
list.isEmpty(); // false
Collections.sort(list);
list.sort(Comparator.reverseOrder());
// Immutable list (Java 9+)
List<String> fixed = List.of("x", "y", "z");
// LinkedList — efficient head/tail ops
Deque<String> deque = new LinkedList<>();
deque.addFirst("first");
deque.addLast("last");
deque.pollFirst();
Map
// HashMap — no order guarantee
Map<String, Integer> map = new HashMap<>();
map.put("alice", 90);
map.put("bob", 85);
map.getOrDefault("charlie", 0); // 0 — safe get
map.putIfAbsent("alice", 100); // won't override
map.containsKey("bob"); // true
map.remove("bob");
map.size(); // 1
// Iterate
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
map.forEach((k, v) -> System.out.println(k + " = " + v));
// Merge and compute
map.merge("alice", 5, Integer::sum); // alice=95
map.computeIfAbsent("dave", k -> 0); // init if missing
// LinkedHashMap — insertion order
Map<String, Integer> ordered = new LinkedHashMap<>();
// TreeMap — sorted by key
Map<String, Integer> sorted = new TreeMap<>();
// Immutable map (Java 9+)
Map<String, Integer> fixed = Map.of("a", 1, "b", 2);
Set
Set<String> set = new HashSet<>(List.of("a", "b", "c"));
set.add("d");
set.contains("a"); // true
set.remove("b");
set.size(); // 3
// Set operations
Set<String> other = new HashSet<>(Set.of("c", "d", "e"));
set.retainAll(other); // intersection: {c, d}
set.addAll(other); // union
set.removeAll(other); // difference
// TreeSet — sorted
Set<Integer> sorted = new TreeSet<>(List.of(3, 1, 2)); // [1, 2, 3]
OOP — Classes and interfaces
// Class with constructor
public class Person {
private final String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String name() { return name; }
public int age() { return age; }
@Override
public String toString() {
return "Person{name='%s', age=%d}".formatted(name, age);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Person p)) return false;
return age == p.age && name.equals(p.name);
}
@Override public int hashCode() {
return Objects.hash(name, age);
}
}
// Inheritance
public class Employee extends Person {
private final String role;
public Employee(String name, int age, String role) {
super(name, age);
this.role = role;
}
@Override public String toString() {
return super.toString() + ", role=" + role;
}
}
// Interface
public interface Drawable {
void draw(); // abstract
default String color() { return "black"; } // default impl
static Drawable noop() { return () -> {}; } // static factory
}
// Record — immutable data class (Java 16+)
record Point(double x, double y) {
// Compact constructor for validation
Point {
if (x < 0 || y < 0) throw new IllegalArgumentException("negative");
}
double distanceTo(Point other) {
return Math.hypot(x - other.x, y - other.y);
}
}
// Sealed interface (Java 17+)
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
// Pattern matching with switch (Java 21 — preview in 17)
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.w() * r.h();
};
}
// Enum
public enum Status {
PENDING, ACTIVE, CLOSED;
public boolean isTerminal() {
return this == CLOSED;
}
}
Generics
// Generic class
public class Pair<A, B> {
private final A first;
private final B second;
public Pair(A first, B second) {
this.first = first; this.second = second;
}
public A first() { return first; }
public B second() { return second; }
}
// Generic method
public <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
// Bounded wildcards
void printAll(List<? extends Number> nums) { /* read-only */ }
void addNumbers(List<? super Integer> list) { list.add(42); }
// PECS: Producer Extends, Consumer Super
Streams (Java 8+)
List<String> words = List.of("apple", "banana", "cherry", "avocado");
// filter + map + collect
List<String> result = words.stream()
.filter(w -> w.startsWith("a"))
.map(String::toUpperCase)
.sorted()
.toList(); // ["APPLE", "AVOCADO"]
// reduce
int total = List.of(1, 2, 3, 4, 5).stream()
.reduce(0, Integer::sum); // 15
// count, min, max
long count = words.stream().filter(w -> w.length() > 5).count();
Optional<String> longest = words.stream()
.max(Comparator.comparingInt(String::length));
// flatMap
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.toList(); // [1, 2, 3, 4]
// groupingBy
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(String::length));
// joining
String joined = words.stream()
.collect(Collectors.joining(", ", "[", "]")); // [apple, banana, ...]
// partitioningBy
Map<Boolean, List<String>> parts = words.stream()
.collect(Collectors.partitioningBy(w -> w.length() > 5));
// IntStream, LongStream, DoubleStream
int sum = IntStream.rangeClosed(1, 100).sum(); // 5050
double avg = IntStream.of(1,2,3,4,5).average().orElse(0.0);
// Parallel stream (use for CPU-bound, large datasets only)
words.parallelStream().filter(w -> isExpensive(w)).toList();
Optional (Java 8+)
Optional<String> opt = Optional.ofNullable(getValue());
// Access
opt.isPresent() // true/false
opt.isEmpty() // Java 11+
opt.get() // throws if empty — avoid
opt.orElse("default")
opt.orElseGet(() -> computeDefault()) // lazy
opt.orElseThrow(() -> new IllegalStateException("missing"))
// Transform
Optional<Integer> len = opt.map(String::length);
Optional<String> upper = opt.map(String::toUpperCase);
Optional<String> filtered = opt.filter(s -> s.length() > 3);
// flatMap — when the function already returns Optional
Optional<String> city = findUser(id)
.flatMap(user -> findAddress(user))
.map(Address::city);
Exception handling
// Checked vs unchecked
// - Checked (must catch or declare): IOException, SQLException
// - Unchecked (RuntimeException): NullPointerException, IllegalArgumentException
// Try-catch-finally
try {
int result = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.err.println("Bad input: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
} finally {
// always runs
}
// Try-with-resources (auto-close)
try (var reader = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("Failed to read file", e);
}
// Custom exception
public class NotFoundException extends RuntimeException {
private final String id;
public NotFoundException(String id) {
super("Not found: " + id);
this.id = id;
}
public String id() { return id; }
}
// Multi-catch
try {
// ...
} catch (IOException | SQLException e) {
log.error("Data error", e);
}
Lambdas and functional interfaces
// Built-in functional interfaces
Function<String, Integer> f = String::length; // T -> R
Consumer<String> c = System.out::println; // T -> void
Supplier<String> s = () -> "hello"; // () -> T
Predicate<String> p = str -> str.isEmpty(); // T -> boolean
BiFunction<String, Integer, String> bi = String::repeat;
// Compose functions
Function<Integer, Integer> times2 = x -> x * 2;
Function<Integer, Integer> plus3 = x -> x + 3;
Function<Integer, Integer> combined = times2.andThen(plus3); // (x*2)+3
Function<Integer, Integer> composed = times2.compose(plus3); // (x+3)*2
// Predicate composition
Predicate<String> notEmpty = Predicate.not(String::isEmpty);
Predicate<String> longWord = s2 -> s2.length() > 5;
Predicate<String> both = notEmpty.and(longWord);
Concurrency basics
// Thread creation
Thread t = new Thread(() -> System.out.println("Running in thread"));
t.start();
t.join(); // wait for completion
// ExecutorService (preferred over raw threads)
ExecutorService exec = Executors.newFixedThreadPool(4);
Future<Integer> future = exec.submit(() -> computeResult());
int result = future.get(); // blocks until done
exec.shutdown();
// CompletableFuture (Java 8+)
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> fetchData())
.thenApply(String::toUpperCase)
.thenApply(s -> "Result: " + s)
.exceptionally(ex -> "Error: " + ex.getMessage());
// Combine multiple futures
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (a, b) -> a + " " + b);
// Virtual threads (Java 21)
Thread.ofVirtual().start(() -> handleRequest());
ExecutorService vExec = Executors.newVirtualThreadPerTaskExecutor();
// Synchronized block
private final Object lock = new Object();
synchronized (lock) {
// critical section
}
// Atomic variables
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(expected, newValue);
// ConcurrentHashMap
ConcurrentHashMap<String, Integer> safeMap = new ConcurrentHashMap<>();
safeMap.compute("key", (k, v) -> v == null ? 1 : v + 1);
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
str1 == str2 |
Compares references, not content | str1.equals(str2) |
int / int integer division |
5 / 2 = 2 not 2.5 |
Cast: (double) 5 / 2 or 5.0 / 2 |
| Modifying list in for-each | ConcurrentModificationException |
Use Iterator or removeIf |
Optional.get() without check |
Throws NoSuchElementException |
Use orElse / orElseThrow |
Mutable object in HashMap key |
Key hash changes, value lost | Use immutable keys (String, record) |
| Not closing streams/connections | Resource leak | Use try-with-resources |
parallelStream() everywhere |
Overhead outweighs benefit | Only for large datasets, CPU-bound work |
Java version quick reference
| Feature | Java version |
|---|---|
| Lambdas, Streams, Optional | Java 8 |
var local type inference |
Java 10 |
String.isBlank(), strip() |
Java 11 |
switch expressions |
Java 14 |
| Records | Java 16 |
| Sealed classes | Java 17 (LTS) |
| Virtual threads | Java 21 (LTS) |
| Pattern matching switch | Java 21 |
Frequently asked questions
What is the difference between == and .equals() in Java?== compares object references (memory addresses). .equals() compares object content. For strings and objects, always use .equals(). For primitives (int, char, etc.), == compares values directly.
When should I use ArrayList vs LinkedList?
Use ArrayList (the default) for almost everything — it has O(1) random access and cache-friendly memory. Use LinkedList only when you frequently insert/remove at both ends and never need random access. In practice, ArrayList wins for most use cases.
What is the difference between Comparable and Comparator?Comparable is implemented by the class itself (natural ordering via compareTo). Comparator is an external comparison strategy. Use Comparator.comparing() to sort without modifying the class — useful for multiple sort orders.
What is autoboxing and when does it cause problems?
Autoboxing is automatic conversion between primitives (int) and wrapper types (Integer). It causes problems when comparing boxed integers with == (use .equals()), when null unboxing throws NullPointerException, and in tight loops where boxing/unboxing adds overhead.
What is the difference between throws and throw?throw triggers an exception: throw new RuntimeException("msg"). throws declares that a method may throw a checked exception: void read() throws IOException. Unchecked exceptions (subclasses of RuntimeException) don't need to be declared.
What is the difference between Runnable and Callable?Runnable has a run() method that returns void and can't throw checked exceptions. Callable<T> has call() that returns T and can throw checked exceptions. Use Callable with ExecutorService.submit() when you need a result or exception propagation.