Java is statically typed, compiled to bytecode and run on the JVM, and it has
one of the biggest standard libraries of any language. The reference below is
grouped by what you are trying to do, and the filter box searches all of it at
once. Type stream and every stream row comes to you, or type Java 21 to see
what that release added.
Every snippet is checked against Java 25, the current long-term support
release, with Eclipse Temurin 25. Java 17 is the baseline, so anything newer
says so in the notes column and you can tell at a glance whether it will run on
the JDK you have. Names like names, people and Person are placeholders for
your own. If you do not have a JDK installed, the official Docker image is the
quickest way to try something:
docker run --rm -it -v "$PWD":/src -w /src eclipse-temurin:25-jdk bash, and
the Docker cheat sheet has the rest. Coming
from C++? The C++ cheat sheet is laid out the
same way, so the two are easy to compare side by side.
Searches the task, the command and the third column. Press / from anywhere on the page.
287 commands
Compiling and running
| Task | Command | Notes |
|---|---|---|
| Check which version you have | java -version javac -version | javac only exists if you installed a JDK, not just a runtime |
| Run one file without compiling it first | java Main.java | Compiles in memory and runs the first class in the file |
| Run a program spread over several files | java Main.java | Java 22+. Finds the other classes in .java files next to it |
| Compile | javac Main.java | Writes Main.class |
| Run a compiled class | java Main | The class name, without .class |
| Compile into a separate folder | javac -d out src/*.java | |
| Run from that folder | java -cp out Main | -cp is short for --class-path |
| Run a class inside a package | java -cp out com.example.app.Main | Folders under out match the package name |
| Add library jars to the class path | java -cp "out:lib/*" Main | Use ; instead of : on Windows |
| Turn on every warning | javac -Xlint:all Main.java | |
| Treat every warning as an error | javac -Xlint:all -Werror Main.java | |
| Compile for an older Java | javac --release 17 Main.java | Also stops you using APIs newer than 17 |
| Try code out interactively | jshell | A REPL. /exit to leave |
| Package into a runnable jar | jar --create --file app.jar --main-class Main -C out . | |
| Run a jar | java -jar app.jar | |
| Give it more memory | java -Xmx2g -jar app.jar | Maximum heap size. -Xms sets the starting size |
| Turn on assert statements | java -ea Main | Asserts are skipped without it |
| Try a preview feature | java --enable-preview --source 25 Main.java | javac needs --enable-preview --release 25 too |
| Build and test a Maven project | mvn package mvn test | |
| Build and test a Gradle project | ./gradlew build ./gradlew test |
java Main.java is the quickest way to run a small program, and since Java 22 it works for programs split across several files as well. Reach for javac, a jar, or Maven or Gradle once the program has dependencies or needs shipping.
Types and wrappers
Java has eight primitive types that hold a value directly, and a wrapper class for each (Integer for int, Double for double and so on) that holds it as an object. Collections and generics only take objects, and Java converts between the two for you, which is called boxing and unboxing.
| Task | Code | Notes |
|---|---|---|
| Integer | int n = 42; | 32 bits on every platform, about plus or minus 2.1 billion |
| Wider integer | long big = 9_000_000_000L; | 64 bits. The _ is a digit separator, and L is needed past the int range |
| Small integers | byte b = 127; short s = 30_000; | byte is signed, -128 to 127 |
| Floating point | double d = 3.14; float f = 3.14f; | double is the default for decimals |
| Boolean | boolean done = false; | Not a number. if (1) does not compile |
| Character | char c = 'A'; | A 16-bit UTF-16 unit, not a byte |
| Let the compiler infer the type | var names = new ArrayList<String>(); | Local variables only. Needs an initialiser |
| Cannot be reassigned | final int maxUsers = 100; | |
| Class constant | static final double TAX_RATE = 0.2; | UPPER_SNAKE_CASE by convention |
| Wrapper type | Integer boxed = 42; | Boxed automatically. Can be null, unlike int |
| Back to a primitive | int n = boxed; | Throws NullPointerException if boxed is null |
| Collections need wrappers | List<Integer> nums = new ArrayList<>(); | List<int> does not compile |
| Compare two wrappers | a.equals(b) | == compares identity, and only works by luck for small numbers |
| Convert to another type | double avg = (double) total / count; | Cast one side, or the division is whole-number |
| Cut a decimal down to an int | int n = (int) 3.99; | 3. Truncates toward zero |
| Text to a number | int n = Integer.parseInt("42"); double d = Double.parseDouble("2.5"); | Throws NumberFormatException on bad input |
| Number to text | String.valueOf(42) Integer.toString(255, 16) | The second gives ff, in base 16 |
| Largest and smallest values | Integer.MAX_VALUE Long.MIN_VALUE | |
| Hex, octal and binary literals | 0xFF 0755 0b1010 | A leading 0 means octal, so 010 is 8 |
| Fail on overflow instead of wrapping | Math.addExact(a, b) | Throws ArithmeticException. a + b silently wraps |
| Exact decimals, for money | new BigDecimal("0.10").add(new BigDecimal("0.20")) | java.math. Build from a String, never a double |
| Integers of any size | BigInteger.TWO.pow(100) |
Strings
A String can never change. Every method that seems to modify one returns a new String, so keep the result: s.toUpperCase() on its own line does nothing.
| Task | Code | Notes |
|---|---|---|
| Make a string | String name = "Ada"; | Double quotes. Single quotes are for one char |
| Length | name.length() | A method, unlike an array's length field |
| One character | name.charAt(0) | Throws StringIndexOutOfBoundsException past the end |
| Compare text | answer.equals("yes") answer.equalsIgnoreCase("YES") | Never == for text |
| Compare when it might be null | "yes".equals(answer) Objects.equals(a, b) | Neither throws on null |
| Alphabetical order | a.compareTo(b) | Negative if a comes first, 0 if equal |
| Join two strings | String full = first + " " + last; | |
| Build one up in a loop | StringBuilder sb = new StringBuilder(); sb.append(word).append(','); String out = sb.toString(); | += in a loop copies the whole string every time |
| Format | String.format("%s is %d", name, age) "%s is %d".formatted(name, age) | %s any value, %d whole number, %.2f two decimals |
| Print formatted | System.out.printf("%.2f%n", price); | %n is the newline for the platform |
| Pad and align | String.format("%-8s|%5d", name, n) | - aligns left, no - aligns right |
| Substring | s.substring(1, 4) | From index 1 up to, but not including, 4 |
| Find text | s.indexOf("cat") | -1 if it is not there |
| Contains, starts with, ends with | s.contains("cat") s.startsWith("/") s.endsWith(".java") | |
| Replace every match | s.replace("cat", "dog") | Plain text. replaceAll takes a regex |
| Split | String[] parts = "a,b,c".split(","); | The argument is a regex, so split on a dot with \\. |
| Join a list | String.join(", ", names) | |
| Trim whitespace | s.strip() | Unicode-aware. The older trim() only knows ASCII spaces |
| Empty or blank | s.isEmpty() s.isBlank() | isBlank is also true for spaces only |
| Change case | s.toUpperCase() s.toLowerCase() | |
| Repeat | "ab".repeat(3) | ababab |
| Reverse | new StringBuilder(s).reverse().toString() | |
| Loop over the characters | for (char c : s.toCharArray()) { } | |
| Test a character | Character.isDigit(c) Character.isLetter(c) Character.isWhitespace(c) | |
| Split into lines | s.lines().toList() | Handles \n and \r\n |
For text across several lines, use a text block: three double quotes, a line break, the text, then three closing double quotes. The config example further down uses one. The indentation common to every line is removed for you.
Arrays
An array has a fixed length set when it is made. Reach for an ArrayList when you need to add or remove items. The helper methods live in java.util.Arrays.
| Task | Code | Notes |
|---|---|---|
| Make one with values | int[] nums = {3, 1, 2}; | |
| Make one with a size | String[] names = new String[5]; | Filled with null. Numbers start at 0, booleans at false |
| Length | nums.length | A field, no brackets |
| Read and write | nums[0] = 10; | ArrayIndexOutOfBoundsException past the end |
| Loop over it | for (int n : nums) { } | |
| Print it | Arrays.toString(nums) | [3, 1, 2]. Printing nums itself gives something like [I@1b6d3586 |
| Sort | Arrays.sort(nums); | |
| Sort with your own rule | Arrays.sort(words, Comparator.comparing(String::length)); | Object arrays only, not int[] |
| Binary search a sorted array | Arrays.binarySearch(nums, 3) | Negative if not found |
| Fill every slot | Arrays.fill(nums, -1); | |
| Copy or resize | int[] bigger = Arrays.copyOf(nums, 10); int[] middle = Arrays.copyOfRange(nums, 1, 3); | |
| Compare contents | Arrays.equals(a, b) | a.equals(b) only checks they are the same array |
| Two dimensions | int[][] grid = new int[3][4]; grid[1][2] = 5; | 3 rows of 4 |
| Print two dimensions | Arrays.deepToString(grid) | |
| Sum, max, average | Arrays.stream(nums).sum() Arrays.stream(nums).max().getAsInt() | |
| Any number of arguments | static int sum(int... nums) { return Arrays.stream(nums).sum(); } | nums is an int[] inside the method |
| Array to a list | List<String> list = List.of(names); | An unmodifiable copy. Arrays.asList gives a fixed-size view instead |
| List to an array | String[] arr = list.toArray(String[]::new); |
Collections: lists, maps and sets
Declare the variable as the interface (List, Map, Set) and create the class (ArrayList, HashMap, HashSet), so the class can be swapped later. Everything here is in java.util.
| Task | Code | Notes |
|---|---|---|
| Fixed list | List<String> names = List.of("Ada", "Linus"); | Unmodifiable: add throws UnsupportedOperationException. No nulls |
| Growable list | List<String> names = new ArrayList<>(); | The default choice |
| Growable copy of a fixed list | new ArrayList<>(List.of("Ada", "Linus")) | |
| Add | names.add("Grace"); names.add(0, "Alan"); | The second inserts at index 0 |
| Read and replace by index | names.get(0) names.set(0, "Barbara") | |
| Size, empty, contains | names.size() names.isEmpty() names.contains("Ada") | |
| Remove a value | names.remove("Ada"); | On a List<Integer>, remove(3) removes index 3. Use remove(Integer.valueOf(3)) |
| Remove every match | names.removeIf(n -> n.startsWith("A")); | Safe, unlike removing inside a for-each loop |
| First and last | names.getFirst() names.getLast() | Java 21. get(0) and get(size() - 1) before that |
| Loop backwards | for (String n : names.reversed()) { } | Java 21 |
| Sort | Collections.sort(names); names.sort(Comparator.reverseOrder()); | |
| Sort by a field, then another | people.sort(Comparator.comparing(Person::age).thenComparing(Person::name)); | |
| Largest first | people.sort(Comparator.comparing(Person::age).reversed()); | |
| Key to value | Map<String, Integer> ages = new HashMap<>(); ages.put("Ada", 36); | No order. put replaces an existing value |
| Fixed map | Map<String, Integer> codes = Map.of("uk", 44, "fr", 33); | Unmodifiable. Up to 10 pairs, Map.ofEntries for more |
| Look up | ages.get("Ada") ages.getOrDefault("Bob", 0) | get returns null for a missing key |
| Is the key there | ages.containsKey("Ada") | |
| Remove a key | ages.remove("Ada"); | |
| Count occurrences | counts.merge(word, 1, Integer::sum); | Starts at 1, adds 1 after that |
| Group into lists | byCity.computeIfAbsent(city, k -> new ArrayList<>()).add(name); | Makes the list the first time a key is seen |
| Add only if missing | ages.putIfAbsent(name, 0); | |
| Loop over keys and values | for (Map.Entry<String, Integer> e : ages.entrySet()) { e.getKey(); e.getValue(); } | Or ages.forEach((k, v) -> ...) |
| Map sorted by key | TreeMap<String, Integer> sorted = new TreeMap<>(ages); | firstKey(), lastKey(), headMap() and friends |
| Map that keeps insertion order | Map<String, Integer> ordered = new LinkedHashMap<>(); | |
| Unique values | Set<String> seen = new HashSet<>(); seen.add(x) | add returns false if it was already there. TreeSet keeps them sorted |
| Union, intersection, difference | a.addAll(b); a.retainAll(b); a.removeAll(b); | Each one changes a |
| Stack | Deque<Integer> stack = new ArrayDeque<>(); stack.push(1); stack.peek(); stack.pop(); | Not the old Stack class |
| Queue | Queue<Integer> queue = new ArrayDeque<>(); queue.offer(1); queue.poll(); | poll returns null when it is empty |
| Always get the smallest next | PriorityQueue<Integer> pq = new PriorityQueue<>(); | new PriorityQueue<>(Comparator.reverseOrder()) for the largest |
| Read-only copy | List<String> safe = List.copyOf(names); | |
| Handy extras | Collections.max(nums) Collections.frequency(nums, 3) Collections.shuffle(nums) |
Removing items from a list inside a for-each loop over the same list throws ConcurrentModificationException. Use removeIf, or an Iterator and its remove method.
Control flow
| Task | Code | Notes |
|---|---|---|
| if, else if, else | if (n > 0) { } else if (n < 0) { } else { } | The condition must be a boolean |
| Pick one of two values | String label = n % 2 == 0 ? "even" : "odd"; | |
| switch without fall-through | switch (day) { case SATURDAY, SUNDAY -> rest(); default -> work(); } | Arrow cases never run into the next one |
| switch that gives a value | int price = switch (size) { case "S" -> 1; case "M" -> 2; default -> 3; }; | A switch expression |
| Several statements in one case | case "L" -> { log("large"); yield 4; } | yield gives the case its value |
| Old-style switch | case 1: go(); break; | Colon cases fall through into the next without break |
| Check a type and use it | if (obj instanceof String s && !s.isEmpty()) { } | s only exists where the check passed |
| switch on the type | String desc = switch (obj) { case Integer i when i > 0 -> "positive"; case String s -> s; case null -> "null"; default -> "other"; }; | Java 21. when adds a condition |
| Take a record apart | if (obj instanceof Point(int x, int y)) { } | Java 21. A record pattern |
| Ignore a part you don't need | case Point(int x, _) -> x | Java 22. Also catch (Exception _) |
| Counting loop | for (int i = 0; i < n; i++) { } | |
| Loop over a list or array | for (String name : names) { } | |
| while and do-while | while (it.hasNext()) { } do { } while (retry()); | do-while always runs at least once |
| Skip to the next round, or stop | continue; break; | |
| Break out of an outer loop | outer: for (int[] row : grid) { for (int v : row) { if (v < 0) break outer; } } | A label on the loop you want to leave |
| Loop with a lambda | names.forEach(System.out::println); |
Classes and objects
| Task | Code | Notes |
|---|---|---|
| Define a class | public class Account { private double balance; } | One public class per file, and the file is named after it |
| Constructor | public Account(String owner) { this.owner = owner; } | No return type. this.owner is the field |
| One constructor calling another | public Account() { this("anonymous"); } | |
| Create an object | Account a = new Account("Ada"); var b = new Account("Bob"); | |
| Method | public void deposit(double amount) { balance += amount; } | |
| Static method | static int add(int a, int b) { return a + b; } | Called on the class: Account.add(1, 2) |
| Shared by every object | private static int count = 0; | |
| Who can see it | public protected private | No keyword means the same package only |
| Inherit | public class Savings extends Account { } | One superclass only |
| Call the parent constructor | public Savings(String owner) { super(owner); } | |
| Check arguments before super() | Savings(double rate) { if (rate < 0) throw new IllegalArgumentException(); super("savings"); } | Java 25. Until then super() had to come first |
| Replace a parent method | @Override public String toString() { return "Account[" + owner + "]"; } | @Override makes a typo a compile error |
| Call the parent's version | super.toString() | |
| Class that must be extended | abstract class Shape { abstract double area(); } | Cannot be created with new |
| Stop subclassing or overriding | final class Money { } final void close() { } | |
| Equality by value | @Override public boolean equals(Object o) { return o instanceof Point p && x == p.x && y == p.y; } | |
| Hash to match | @Override public int hashCode() { return Objects.hash(x, y); } | Override both or neither, or HashMap and HashSet break |
| Fixed set of values | enum Size { SMALL, MEDIUM, LARGE } | Compare with == |
| Enum with fields | enum Coin { PENNY(1), DIME(10); final int cents; Coin(int cents) { this.cents = cents; } } | |
| Enum helpers | Size.valueOf("SMALL") Size.values() s.name() s.ordinal() | |
| Class inside a class | static class Node { int value; Node next; } | Leave out static and each Node holds a hidden link to the outer object |
| One-off implementation | Runnable r = new Runnable() { public void run() { go(); } }; | An anonymous class. A lambda is shorter |
| Program entry point | public static void main(String[] args) { } | |
| Hello world, short form | void main() { IO.println("Hello"); } | Java 25. A whole file with no class around it |
Interfaces and records
| Task | Code | Notes |
|---|---|---|
| Define an interface | interface Shape { double area(); } | Its methods are public and abstract unless they have a body |
| Implement it | class Circle implements Shape { public double area() { return Math.PI * r * r; } } | A class can implement several, comma separated |
| Method with a body | default String describe() { return "area " + area(); } | Implementations get it for free and may override it |
| Static helper | static Shape unit() { return new Circle(1); } | Called as Shape.unit() |
| Constant | int SIDES = 4; | Fields in an interface are public static final |
| Interface for a lambda | @FunctionalInterface interface Parser { int parse(String s); } | Exactly one abstract method |
| Data class in one line | record Point(int x, int y) { } | Final fields, constructor, accessors, equals, hashCode and toString |
| Read a field | p.x() | The accessor is x(), not getX() |
| Validate in the constructor | record Range(int lo, int hi) { Range { if (lo > hi) throw new IllegalArgumentException(); } } | A compact constructor. The fields are set after it runs |
| Add methods to a record | record Point(int x, int y) { static Point origin() { return new Point(0, 0); } double length() { return Math.hypot(x, y); } } | |
| Change one field | Point moved = new Point(p.x(), p.y() + 1); | Records cannot change, so make a new one |
| Only these types may implement it | sealed interface Shape permits Circle, Square { } | Each must be final, sealed or non-sealed. Records are final already |
| switch with no default | double a = switch (shape) { case Circle c -> c.area(); case Square s -> s.area(); }; | Java 21. The compiler knows the list is complete |
A record can implement interfaces but cannot extend a class, and its fields cannot be reassigned. If the fields hold lists or other mutable objects, the record is only as immutable as they are, so copy them in with List.copyOf.
Generics
| Task | Code | Notes |
|---|---|---|
| Generic class | class Box<T> { private T value; T get() { return value; } } | |
| Use it | Box<String> box = new Box<>(); | The empty <> (diamond) copies the type from the left |
| Generic method | static <T> T firstOf(List<T> list) { return list.get(0); } | The <T> goes before the return type |
| Two type parameters | record Pair<A, B>(A first, B second) { } | |
| Generic interface | interface Repository<T, ID> { Optional<T> findById(ID id); } | |
| Only accept certain types | static <T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) >= 0 ? a : b; } | extends means extends or implements here |
| Several bounds | <T extends Number & Comparable<T>> | At most one class, and it comes first |
| Read from a list of any subtype | static double sum(List<? extends Number> nums) | Takes List<Integer> or List<Double>. You cannot add to it |
| Write into a list of any supertype | static void fill(List<? super Integer> out) | Takes List<Integer>, List<Number> or List<Object> |
| Any type at all | static void printAll(List<?> items) | |
| No primitives | List<Integer> nums; | List<int> does not compile |
| Make a T when you need one | static <T> List<T> fresh(Supplier<T> make, int n) | new T() does not compile. Pass a Supplier or a Class<T> |
| Raw type | List raw = new ArrayList(); | Compiles, but with a warning. Legacy code only |
| Silence one warning you have checked | @SuppressWarnings("unchecked") |
Generic types are erased when the code is compiled, so at run time a List<String> is just a List. That is why instanceof List<String>, new T() and new T[10] do not compile.
Exceptions
Checked exceptions (IOException and anything else extending Exception) must be caught or declared with throws, or the code does not compile. Unchecked ones (anything extending RuntimeException) need neither.
| Task | Code | Notes |
|---|---|---|
| Catch | try { risky(); } catch (IOException e) { System.err.println(e.getMessage()); } | |
| Catch specific types first | catch (FileNotFoundException e) { } catch (IOException e) { } | The compiler rejects them in the wrong order |
| Catch several the same way | catch (NumberFormatException | ArithmeticException e) { } | |
| Always run | finally { cleanup(); } | Runs whether the try succeeded, threw or returned |
| Close a resource for you | try (var reader = Files.newBufferedReader(path)) { } | Closed however the block ends. Works for anything AutoCloseable |
| Several resources | try (var in = Files.newInputStream(src); var out = Files.newOutputStream(dst)) { in.transferTo(out); } | Closed in reverse order |
| Throw | throw new IllegalArgumentException("age must be positive"); | |
| Declare a checked exception | void load() throws IOException { } | |
| Your own checked exception | class OutOfStockException extends Exception { OutOfStockException(String msg) { super(msg); } } | |
| Your own unchecked exception | class ConfigException extends RuntimeException { } | |
| Wrap one and keep the cause | throw new IllegalStateException("could not load config", e); | Pass e on, or its stack trace is lost |
| Checked IOException as unchecked | throw new UncheckedIOException(e); | Useful inside a lambda, which cannot throw checked exceptions |
| Print the stack trace | e.printStackTrace(); | |
| Ignore the exception object | catch (NumberFormatException _) { } | Java 22 |
| Check an assumption | assert n > 0 : "n must be positive"; | Only runs with java -ea |
| Common unchecked exceptions | NullPointerException IllegalArgumentException IllegalStateException IndexOutOfBoundsException | Throw the last three yourself for bad input and bad state |
Lambdas and method references
A lambda is a function written inline, and it can go anywhere a functional interface is expected: an interface with exactly one abstract method. The standard ones are in java.util.function.
| Task | Code | Notes |
|---|---|---|
| No arguments | Runnable hello = () -> System.out.println("hi"); | |
| One argument | Function<Integer, Integer> twice = x -> x * 2; | |
| Several arguments | BinaryOperator<Integer> add = (a, b) -> a + b; | |
| Several statements | Function<String, Integer> score = s -> { int n = s.length(); return n * n; }; | |
| Call it | twice.apply(5) hello.run() | The method name comes from the interface |
| Refer to a static method | Function<String, Integer> parse = Integer::parseInt; | |
| Refer to a method of one object | Consumer<String> print = System.out::println; | |
| Refer to a method of whatever is passed | Function<String, Integer> len = String::length; | |
| Refer to a constructor | Supplier<List<String>> make = ArrayList::new; | |
| Takes a value, returns a value | Function<T, R> BiFunction<T, U, R> | apply |
| Takes nothing, returns a value | Supplier<T> | get |
| Takes a value, returns nothing | Consumer<T> BiConsumer<T, U> | accept |
| Takes a value, returns a boolean | Predicate<T> | test |
| Same type in and out | UnaryOperator<T> BinaryOperator<T> | |
| Avoid boxing ints | IntPredicate IntFunction<R> ToIntFunction<T> IntUnaryOperator | Also Long and Double versions |
| Chain two functions | twice.andThen(x -> x + 1).apply(3) | 7. compose runs the other one first |
| Combine conditions | isLong.and(isUpper) isLong.negate() Predicate.not(String::isBlank) | |
| Use a local variable inside | int limit = 10; Predicate<Integer> small = x -> x < limit; | The variable must never be reassigned (effectively final) |
Streams
A stream runs a pipeline over a collection: a source, any number of steps like filter and map, and one final step that produces the result. Nothing runs until that last step, and a stream can only be used once. The collectors are in java.util.stream.Collectors.
| Task | Code | Notes |
|---|---|---|
| From a list | names.stream() | |
| From values | Stream.of("a", "b", "c") | |
| From an array | Arrays.stream(nums) | |
| Numbers in a range | IntStream.range(0, 10) | 0 to 9. rangeClosed(1, 10) includes 10 |
| Keep matches | names.stream().filter(n -> n.length() > 3) | |
| Transform each one | names.stream().map(String::toUpperCase) | |
| Collect into a list | List<String> upper = names.stream().map(String::toUpperCase).toList(); | Unmodifiable. collect(Collectors.toList()) gives a mutable one |
| Collect into a set | .collect(Collectors.toSet()) | |
| Collect into a map | .collect(Collectors.toMap(Person::name, Person::age)) | Throws IllegalStateException on a duplicate key unless you pass a merge function |
| Group | Map<String, List<Person>> byCity = people.stream().collect(Collectors.groupingBy(Person::city)); | |
| Count each group | Collectors.groupingBy(Person::city, Collectors.counting()) | Gives Map<String, Long> |
| Split into true and false | Collectors.partitioningBy(n -> n > 0) | |
| Join into one string | Collectors.joining(", ", "[", "]") | Separator, prefix, suffix |
| Sort | .sorted() .sorted(Comparator.comparing(Person::age)) | |
| Drop duplicates, take some, skip some | .distinct() .limit(10) .skip(5) | |
| Sum and average | people.stream().mapToInt(Person::age).sum() .average() | average returns an OptionalDouble |
| Smallest and largest | .min(Comparator.naturalOrder()) .max(Comparator.comparing(Person::age)) | Return an Optional |
| How many | .count() | A long |
| Any, all or none | .anyMatch(n -> n > 10) .allMatch(n -> n > 0) .noneMatch(String::isBlank) | |
| First match | .filter(n -> n > 10).findFirst() | An Optional |
| Combine into one value | .reduce(0, Integer::sum) | |
| Flatten lists of lists | .flatMap(List::stream) | |
| Do something with each | .forEach(System.out::println) | |
| Endless sequence | Stream.iterate(1, n -> n * 2).limit(10) | 1, 2, 4 up to 512 |
| Stop at the first miss | .takeWhile(n -> n < 100) | dropWhile does the opposite |
| Groups of three | .gather(Gatherers.windowFixed(3)) | Java 24. java.util.stream.Gatherers |
| Primitive stream to objects | IntStream.range(0, 5).boxed().toList() |
parallelStream() splits the work across threads. It only pays off for large amounts of CPU-bound work, and the lambdas must not touch shared state.
Optional
Optional is a return type that says a method might not have a value, so the caller has to deal with the empty case instead of getting a null. It is meant for return values, not for fields or parameters.
| Task | Code | Notes |
|---|---|---|
| Return a value | return Optional.of(user); | Throws NullPointerException if user is null |
| Return a value that might be null | return Optional.ofNullable(cache.get(id)); | |
| Return nothing | return Optional.empty(); | |
| Value or a default | .orElse("guest") | |
| Value or a computed default | .orElseGet(() -> loadDefault()) | Only runs when empty. orElse always evaluates its argument |
| Value or throw | .orElseThrow() .orElseThrow(() -> new NotFoundException(id)) | The first throws NoSuchElementException |
| Transform it if it is there | .map(User::email) | |
| Transform with a method that returns Optional | .flatMap(User::manager) | |
| Keep it only if it matches | .filter(u -> u.isActive()) | |
| Do something if it is there | .ifPresent(u -> send(u)) | |
| Handle both cases | .ifPresentOrElse(u -> send(u), () -> log("no user")) | |
| Check | .isPresent() .isEmpty() | |
| Try another source | .or(() -> findInDatabase(id)) | |
| Drop the empty ones from a stream | .flatMap(Optional::stream) |
Calling get() on an empty Optional throws NoSuchElementException, which is no better than the null it replaced. Use orElse, orElseThrow or map instead.
Common packages
| Task | Code | Notes |
|---|---|---|
| Today's date | LocalDate today = LocalDate.now(); | java.time. Never java.util.Date in new code |
| A particular date | LocalDate.of(2026, 9, 27) | Months are 1 to 12 |
| Date arithmetic | today.plusDays(30) today.minusMonths(1) | Returns a new date |
| Days between two dates | ChronoUnit.DAYS.between(start, end) | |
| Parse and format a date | LocalDate.parse("2026-09-27") today.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) | |
| A moment in time | Instant.now() | UTC. For timestamps |
| Date and time in a zone | ZonedDateTime.now(ZoneId.of("Europe/London")) | |
| A length of time | Duration.ofMinutes(90).toHours() | 1. Duration.between(a, b) measures one |
| Read a whole file | String text = Files.readString(Path.of("notes.txt")); | java.nio.file. Throws IOException |
| Read the lines | List<String> lines = Files.readAllLines(path); | |
| Stream a big file line by line | try (Stream<String> lines = Files.lines(path)) { } | Close it, which try does for you |
| Write a file | Files.writeString(path, text); | Replaces it. Add StandardOpenOption.APPEND to add to the end |
| Check and create | Files.exists(path) Files.createDirectories(dir) | |
| List a folder | try (Stream<Path> files = Files.list(dir)) { } | |
| Read from the keyboard | String name = IO.readln("Name: "); | Java 25. new Scanner(System.in).nextLine() before that |
| Random number | int roll = ThreadLocalRandom.current().nextInt(1, 7); | 1 to 6. The upper bound is excluded |
| Maths | Math.max(a, b) Math.abs(x) Math.pow(2, 10) Math.sqrt(x) | |
| Round | Math.round(2.5) | 3, but Math.round(-2.5) is -2. It rounds half up |
| Keep a number within bounds | Math.clamp(value, 0, 100) | Java 21 |
| Match a regex | Matcher m = Pattern.compile("(\\w+)@(\\w+)").matcher(email); if (m.matches()) { m.group(1); } | java.util.regex. find() for a match anywhere |
| Replace with a regex | s.replaceAll("\\s+", " ") | |
| Fail fast on null | this.name = Objects.requireNonNull(name, "name"); | |
| Unique ID | UUID.randomUUID().toString() | |
| Environment variable | System.getenv("HOME") | null if it is not set |
| Time something | long start = System.nanoTime(); | For measuring. currentTimeMillis is for the date |
| HTTP GET | HttpResponse<String> res = HttpClient.newHttpClient().send(HttpRequest.newBuilder(URI.create(url)).build(), HttpResponse.BodyHandlers.ofString()); | java.net.http. res.statusCode() and res.body() |
| Thousands of blocking tasks | try (var pool = Executors.newVirtualThreadPerTaskExecutor()) { pool.submit(() -> fetch(url)); } | Java 21. Virtual threads. Waits for every task at the closing brace |
| Thread-safe map and counter | new ConcurrentHashMap<String, Integer>() new AtomicInteger().incrementAndGet() | java.util.concurrent |
| Import a whole module | import module java.base; | Java 25. Every public class in java.util, java.io, java.time and the rest |
Counting words, start to finish
Splitting a string, grouping with a collector, and sorting with a chained comparator in one program. It prints the three most common words, ties broken alphabetically.
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Words {
public static void main(String[] args) {
String text = "the cat sat on the mat and the cat slept";
Map<String, Long> counts = Arrays.stream(text.split(" "))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// A HashMap has no order, so sort the entries on the way out:
// most frequent first, then A to Z.
counts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey()))
.limit(3)
.forEach(e -> System.out.printf("%-5s %d%n", e.getKey(), e.getValue()));
// the 3
// cat 2
// and 1
}
}Save it as Words.java and run it straight from source:
java Words.javaSorting a list of objects, with List.sort, Collections.sort or a stream's
sorted, uses TimSort: a stable
merge sort that finds runs
already in order and uses
insertion sort to build short
ones. Arrays.sort on an int[] or other primitive array uses a dual-pivot
quick sort instead, which is not
stable, but with plain numbers there is no way to tell. All three are on the
site as step-through visualisations.
Records, sealed interfaces and pattern matching
A sealed interface lists every type that can implement it, so a switch over it
needs no default: add a new shape and every switch that forgot it stops
compiling. The records supply the constructors, accessors and toString.
import java.util.List;
public class Shapes {
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle(double w, double h) -> w * h;
case Square(double side) -> side * side;
};
}
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Rectangle(3, 4), new Square(2));
double total = 0;
for (Shape shape : shapes) {
System.out.printf("%-34s %6.2f%n", shape, area(shape));
total += area(shape);
}
System.out.printf("%-34s %6.2f%n", "total", total);
// Circle[radius=1.0] 3.14
// Rectangle[width=3.0, height=4.0] 12.00
// Square[side=2.0] 4.00
// total 19.14
}
}
sealed interface Shape permits Circle, Rectangle, Square {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {
Rectangle {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("sides must be positive");
}
}
}
record Square(double side) implements Shape {}java Shapes.java runs the first class in the file, which is why Shapes comes
before the types it uses. Pattern matching in switch needs Java 21 or later. The switch uses both kinds of pattern: case Circle c
matches the type and names it, and case Rectangle(double w, double h) takes
the record apart into its fields in one step.
Reading a file, with exceptions and Optional
Try-with-resources closes the file however the block ends, a checked exception
is caught where there is something useful to do about it, and Optional handles
the lines that are not settings without a single null.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class Config {
record Setting(String key, String value) {}
// "port = 8080" becomes a Setting. Comments, blanks and junk become empty.
static Optional<Setting> parse(String line) {
int eq = line.indexOf('=');
if (line.isBlank() || line.startsWith("#") || eq < 0) {
return Optional.empty();
}
return Optional.of(new Setting(line.substring(0, eq).strip(), line.substring(eq + 1).strip()));
}
static List<Setting> load(Path path) throws IOException {
try (Stream<String> lines = Files.lines(path)) {
return lines.map(Config::parse).flatMap(Optional::stream).toList();
}
}
public static void main(String[] args) throws IOException {
Path path = Path.of("app.properties");
Files.writeString(path, """
# sample settings
port = 8080
host=localhost
not a setting
""");
List<Setting> settings;
try {
settings = load(path);
} catch (NoSuchFileException e) {
System.err.println("No config at " + e.getFile() + ", using defaults");
settings = List.of();
}
int port = settings.stream()
.filter(s -> s.key().equals("port"))
.findFirst()
.map(s -> Integer.parseInt(s.value()))
.orElse(80);
System.out.println(settings.size() + " settings, port " + port);
// 2 settings, port 8080
}
}NoSuchFileException is a subclass of IOException, so catching it handles the
one failure that has a sensible fallback and lets every other I/O error carry on
up to main. The text block (the """ string) keeps its line breaks, and the
indentation shared by every line is stripped.
Generics and wildcards, start to finish
A generic class, a bounded generic method, and the two wildcards side by side.
The rule for which wildcard to use is producer extends, consumer super: a list
you only read from takes ? extends T, and a list you only add to takes
? super T.
import java.util.ArrayList;
import java.util.List;
public class Generics {
// Works for any type. T is fixed when a Stack is created.
static class Stack<T> {
private final List<T> items = new ArrayList<>();
void push(T item) {
items.add(item);
}
T pop() {
if (items.isEmpty()) {
throw new IllegalStateException("stack is empty");
}
return items.removeLast();
}
}
// Only for types that can be compared with themselves.
// Reads from values, so values is ? extends T.
static <T extends Comparable<? super T>> T largest(List<? extends T> values) {
T best = values.getFirst();
for (T value : values) {
if (value.compareTo(best) > 0) {
best = value;
}
}
return best;
}
// Adds to out, so out is ? super Integer: a List<Integer>,
// List<Number> or List<Object> all work.
static void addCountingNumbers(List<? super Integer> out, int count) {
for (int i = 1; i <= count; i++) {
out.add(i);
}
}
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("first");
stack.push("second");
System.out.println(stack.pop()); // second
System.out.println(largest(List.of(3, 9, 4))); // 9
System.out.println(largest(List.of("pear", "apple"))); // pear
List<Number> numbers = new ArrayList<>();
addCountingNumbers(numbers, 3);
numbers.add(2.5);
System.out.println(numbers); // [1, 2, 3, 2.5]
// stack.push(42); // error: 42 is not a String
}
}removeLast and getFirst arrived in Java 21 with sequenced collections. On
Java 17, use items.remove(items.size() - 1) and values.get(0).
Gotchas
The mistakes that turn up in almost every Java codebase at some point.
| Looks right | What actually happens | Do this instead |
|---|---|---|
if (name == "Ada") | Compares object identity, so it can be false for the same text | "Ada".equals(name) |
Integer a = 128, b = 128; a == b | false. Only -128 to 127 are cached, so small numbers happen to pass | a.equals(b) |
nums.remove(3) on a List<Integer> | Removes the item at index 3, not the value 3 | nums.remove(Integer.valueOf(3)) |
List.of(1, 2).add(3) | UnsupportedOperationException: List.of is unmodifiable | new ArrayList<>(List.of(1, 2)) |
Arrays.asList(arr).add(x) | UnsupportedOperationException: the list is a fixed-size view of the array | Copy it into a new ArrayList<>(...) |
Removing from a list inside for (String s : list) | ConcurrentModificationException | list.removeIf(...) |
int avg = total / count; then (double) it | The division already happened in whole numbers | (double) total / count |
s.toUpperCase(); on its own line | Strings never change, so the result is thrown away | s = s.toUpperCase(); |
"1.2.3".split(".") | An empty array: the argument is a regex and . matches everything | split("\\.") |
int n = map.get(key); for a missing key | get returns null, and unboxing null throws NullPointerException | map.getOrDefault(key, 0) |
Overriding equals but not hashCode | Equal objects land in different buckets, so HashSet and HashMap lose them | Override both, or use a record |
optional.get() | NoSuchElementException when it is empty | orElse, orElseThrow or map |
Reusing a stream after .toList() | IllegalStateException: a stream runs once | Call .stream() again |
Collectors.toMap(...) with a repeated key | IllegalStateException: Duplicate key | Pass a merge function: toMap(k, v, (a, b) -> a) |
new BigDecimal(0.1) | 0.1000000000000000055511151231257827..., the double's exact value | new BigDecimal("0.1") |
System.out.println('a' + 'b'); | Two chars add as numbers, so it prints 195 | "" + 'a' + 'b' |
Common questions
Which version of Java does this cheat sheet cover?
Java 25, the current long-term support (LTS) release, checked with Eclipse Temurin 25. Plenty of projects still run on Java 17 or 21, the two LTS releases before it, so Java 17 is the baseline here: anything that needs a newer version, such as pattern matching in switch (Java 21), sequenced collections like getFirst (Java 21) or the short void main form (Java 25), says so in the notes column.
What is the difference between == and equals in Java?
== checks whether two references point at the same object, and equals checks whether two objects hold the same value. For primitives such as int and double, == compares values and is correct. For Strings, wrappers like Integer, and any other object, use equals. Two Strings with the same text can be different objects, so == can be false even when the text matches, and it will fail at the worst possible moment rather than every time.
What is the difference between the JDK, the JRE and the JVM?
The JVM (Java Virtual Machine) runs compiled Java bytecode. The JRE (Java Runtime Environment) is the JVM plus the standard library, enough to run a Java program. The JDK (Java Development Kit) is the JRE plus the tools to build programs: javac, jar, jshell and the rest. To write Java you want a JDK. Eclipse Temurin, Oracle JDK, Amazon Corretto and Microsoft Build of OpenJDK are all builds of the same OpenJDK source.
What is the difference between checked and unchecked exceptions?
A checked exception, such as IOException, extends Exception, and the compiler forces every caller to either catch it or declare it with throws. An unchecked exception, such as IllegalArgumentException or NullPointerException, extends RuntimeException, and the compiler leaves it alone. Checked exceptions are for failures a caller can reasonably recover from, like a missing file. Unchecked ones are for bugs and bad input.
When should I use a record instead of a class?
Use a record for a type that is just its data: a point, a request body, a row from a query. You get the constructor, accessors, equals, hashCode and toString for free, and the fields can never be reassigned. Use a class when the object has state that changes over time, needs to extend another class, or has to hide how its data is stored. Records arrived in Java 16, so they are available on every current LTS.
Should I use ArrayList or LinkedList?
ArrayList, almost always. It reads by index in constant time and its memory layout suits the CPU cache, so it is usually faster even for inserts in the middle of small and medium lists. LinkedList only wins when you insert and remove at both ends constantly, and ArrayDeque does that better too. Declare the variable as List so you can change your mind later without touching the rest of the code.
How do I run a Java program without compiling it first?
Run java Main.java. Since Java 11 the java launcher compiles a source file in memory and runs it, with no .class files left behind, and since Java 22 it also finds other classes the program uses in .java files in the same folder. Java 25 goes further: a file can hold just void main() with no class around it. For anything with dependencies, use Maven or Gradle.
Is Java the same as JavaScript?
No. The names are similar for marketing reasons from the 1990s, but they are different languages. Java is statically typed and compiled to bytecode that runs on the JVM, and it is used mostly for back-end services, Android apps and large enterprise systems. JavaScript is dynamically typed and runs in the browser and on Node.js. The syntax looks alike in places because both borrowed from C.
