Compiled and systems languages cheat sheetJava logo

Java cheat sheet

Java syntax on one page, grouped by what you are trying to do: collections, records, generics, streams, Optional and exceptions, checked on Java 25.

Last updated

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.

Compiling and running

TaskCommandNotes
Check which version you havejava -version javac -versionjavac only exists if you installed a JDK, not just a runtime
Run one file without compiling it firstjava Main.javaCompiles in memory and runs the first class in the file
Run a program spread over several filesjava Main.javaJava 22+. Finds the other classes in .java files next to it
Compilejavac Main.javaWrites Main.class
Run a compiled classjava MainThe class name, without .class
Compile into a separate folderjavac -d out src/*.java
Run from that folderjava -cp out Main-cp is short for --class-path
Run a class inside a packagejava -cp out com.example.app.MainFolders under out match the package name
Add library jars to the class pathjava -cp "out:lib/*" MainUse ; instead of : on Windows
Turn on every warningjavac -Xlint:all Main.java
Treat every warning as an errorjavac -Xlint:all -Werror Main.java
Compile for an older Javajavac --release 17 Main.javaAlso stops you using APIs newer than 17
Try code out interactivelyjshellA REPL. /exit to leave
Package into a runnable jarjar --create --file app.jar --main-class Main -C out .
Run a jarjava -jar app.jar
Give it more memoryjava -Xmx2g -jar app.jarMaximum heap size. -Xms sets the starting size
Turn on assert statementsjava -ea MainAsserts are skipped without it
Try a preview featurejava --enable-preview --source 25 Main.javajavac needs --enable-preview --release 25 too
Build and test a Maven projectmvn 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.

TaskCodeNotes
Integerint n = 42;32 bits on every platform, about plus or minus 2.1 billion
Wider integerlong big = 9_000_000_000L;64 bits. The _ is a digit separator, and L is needed past the int range
Small integersbyte b = 127; short s = 30_000;byte is signed, -128 to 127
Floating pointdouble d = 3.14; float f = 3.14f;double is the default for decimals
Booleanboolean done = false;Not a number. if (1) does not compile
Characterchar c = 'A';A 16-bit UTF-16 unit, not a byte
Let the compiler infer the typevar names = new ArrayList<String>();Local variables only. Needs an initialiser
Cannot be reassignedfinal int maxUsers = 100;
Class constantstatic final double TAX_RATE = 0.2;UPPER_SNAKE_CASE by convention
Wrapper typeInteger boxed = 42;Boxed automatically. Can be null, unlike int
Back to a primitiveint n = boxed;Throws NullPointerException if boxed is null
Collections need wrappersList<Integer> nums = new ArrayList<>();List<int> does not compile
Compare two wrappersa.equals(b)== compares identity, and only works by luck for small numbers
Convert to another typedouble avg = (double) total / count;Cast one side, or the division is whole-number
Cut a decimal down to an intint n = (int) 3.99;3. Truncates toward zero
Text to a numberint n = Integer.parseInt("42"); double d = Double.parseDouble("2.5");Throws NumberFormatException on bad input
Number to textString.valueOf(42) Integer.toString(255, 16)The second gives ff, in base 16
Largest and smallest valuesInteger.MAX_VALUE Long.MIN_VALUE
Hex, octal and binary literals0xFF 0755 0b1010A leading 0 means octal, so 010 is 8
Fail on overflow instead of wrappingMath.addExact(a, b)Throws ArithmeticException. a + b silently wraps
Exact decimals, for moneynew BigDecimal("0.10").add(new BigDecimal("0.20"))java.math. Build from a String, never a double
Integers of any sizeBigInteger.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.

TaskCodeNotes
Make a stringString name = "Ada";Double quotes. Single quotes are for one char
Lengthname.length()A method, unlike an array's length field
One charactername.charAt(0)Throws StringIndexOutOfBoundsException past the end
Compare textanswer.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 ordera.compareTo(b)Negative if a comes first, 0 if equal
Join two stringsString full = first + " " + last;
Build one up in a loopStringBuilder sb = new StringBuilder(); sb.append(word).append(','); String out = sb.toString();+= in a loop copies the whole string every time
FormatString.format("%s is %d", name, age) "%s is %d".formatted(name, age)%s any value, %d whole number, %.2f two decimals
Print formattedSystem.out.printf("%.2f%n", price);%n is the newline for the platform
Pad and alignString.format("%-8s|%5d", name, n)- aligns left, no - aligns right
Substrings.substring(1, 4)From index 1 up to, but not including, 4
Find texts.indexOf("cat")-1 if it is not there
Contains, starts with, ends withs.contains("cat") s.startsWith("/") s.endsWith(".java")
Replace every matchs.replace("cat", "dog")Plain text. replaceAll takes a regex
SplitString[] parts = "a,b,c".split(",");The argument is a regex, so split on a dot with \\.
Join a listString.join(", ", names)
Trim whitespaces.strip()Unicode-aware. The older trim() only knows ASCII spaces
Empty or blanks.isEmpty() s.isBlank()isBlank is also true for spaces only
Change cases.toUpperCase() s.toLowerCase()
Repeat"ab".repeat(3)ababab
Reversenew StringBuilder(s).reverse().toString()
Loop over the charactersfor (char c : s.toCharArray()) { }
Test a characterCharacter.isDigit(c) Character.isLetter(c) Character.isWhitespace(c)
Split into liness.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.

TaskCodeNotes
Make one with valuesint[] nums = {3, 1, 2};
Make one with a sizeString[] names = new String[5];Filled with null. Numbers start at 0, booleans at false
Lengthnums.lengthA field, no brackets
Read and writenums[0] = 10;ArrayIndexOutOfBoundsException past the end
Loop over itfor (int n : nums) { }
Print itArrays.toString(nums)[3, 1, 2]. Printing nums itself gives something like [I@1b6d3586
SortArrays.sort(nums);
Sort with your own ruleArrays.sort(words, Comparator.comparing(String::length));Object arrays only, not int[]
Binary search a sorted arrayArrays.binarySearch(nums, 3)Negative if not found
Fill every slotArrays.fill(nums, -1);
Copy or resizeint[] bigger = Arrays.copyOf(nums, 10); int[] middle = Arrays.copyOfRange(nums, 1, 3);
Compare contentsArrays.equals(a, b)a.equals(b) only checks they are the same array
Two dimensionsint[][] grid = new int[3][4]; grid[1][2] = 5;3 rows of 4
Print two dimensionsArrays.deepToString(grid)
Sum, max, averageArrays.stream(nums).sum() Arrays.stream(nums).max().getAsInt()
Any number of argumentsstatic int sum(int... nums) { return Arrays.stream(nums).sum(); }nums is an int[] inside the method
Array to a listList<String> list = List.of(names);An unmodifiable copy. Arrays.asList gives a fixed-size view instead
List to an arrayString[] 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.

TaskCodeNotes
Fixed listList<String> names = List.of("Ada", "Linus");Unmodifiable: add throws UnsupportedOperationException. No nulls
Growable listList<String> names = new ArrayList<>();The default choice
Growable copy of a fixed listnew ArrayList<>(List.of("Ada", "Linus"))
Addnames.add("Grace"); names.add(0, "Alan");The second inserts at index 0
Read and replace by indexnames.get(0) names.set(0, "Barbara")
Size, empty, containsnames.size() names.isEmpty() names.contains("Ada")
Remove a valuenames.remove("Ada");On a List<Integer>, remove(3) removes index 3. Use remove(Integer.valueOf(3))
Remove every matchnames.removeIf(n -> n.startsWith("A"));Safe, unlike removing inside a for-each loop
First and lastnames.getFirst() names.getLast()Java 21. get(0) and get(size() - 1) before that
Loop backwardsfor (String n : names.reversed()) { }Java 21
SortCollections.sort(names); names.sort(Comparator.reverseOrder());
Sort by a field, then anotherpeople.sort(Comparator.comparing(Person::age).thenComparing(Person::name));
Largest firstpeople.sort(Comparator.comparing(Person::age).reversed());
Key to valueMap<String, Integer> ages = new HashMap<>(); ages.put("Ada", 36);No order. put replaces an existing value
Fixed mapMap<String, Integer> codes = Map.of("uk", 44, "fr", 33);Unmodifiable. Up to 10 pairs, Map.ofEntries for more
Look upages.get("Ada") ages.getOrDefault("Bob", 0)get returns null for a missing key
Is the key thereages.containsKey("Ada")
Remove a keyages.remove("Ada");
Count occurrencescounts.merge(word, 1, Integer::sum);Starts at 1, adds 1 after that
Group into listsbyCity.computeIfAbsent(city, k -> new ArrayList<>()).add(name);Makes the list the first time a key is seen
Add only if missingages.putIfAbsent(name, 0);
Loop over keys and valuesfor (Map.Entry<String, Integer> e : ages.entrySet()) { e.getKey(); e.getValue(); }Or ages.forEach((k, v) -> ...)
Map sorted by keyTreeMap<String, Integer> sorted = new TreeMap<>(ages);firstKey(), lastKey(), headMap() and friends
Map that keeps insertion orderMap<String, Integer> ordered = new LinkedHashMap<>();
Unique valuesSet<String> seen = new HashSet<>(); seen.add(x)add returns false if it was already there. TreeSet keeps them sorted
Union, intersection, differencea.addAll(b); a.retainAll(b); a.removeAll(b);Each one changes a
StackDeque<Integer> stack = new ArrayDeque<>(); stack.push(1); stack.peek(); stack.pop();Not the old Stack class
QueueQueue<Integer> queue = new ArrayDeque<>(); queue.offer(1); queue.poll();poll returns null when it is empty
Always get the smallest nextPriorityQueue<Integer> pq = new PriorityQueue<>();new PriorityQueue<>(Comparator.reverseOrder()) for the largest
Read-only copyList<String> safe = List.copyOf(names);
Handy extrasCollections.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

TaskCodeNotes
if, else if, elseif (n > 0) { } else if (n < 0) { } else { }The condition must be a boolean
Pick one of two valuesString label = n % 2 == 0 ? "even" : "odd";
switch without fall-throughswitch (day) { case SATURDAY, SUNDAY -> rest(); default -> work(); }Arrow cases never run into the next one
switch that gives a valueint price = switch (size) { case "S" -> 1; case "M" -> 2; default -> 3; };A switch expression
Several statements in one casecase "L" -> { log("large"); yield 4; }yield gives the case its value
Old-style switchcase 1: go(); break;Colon cases fall through into the next without break
Check a type and use itif (obj instanceof String s && !s.isEmpty()) { }s only exists where the check passed
switch on the typeString 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 apartif (obj instanceof Point(int x, int y)) { }Java 21. A record pattern
Ignore a part you don't needcase Point(int x, _) -> xJava 22. Also catch (Exception _)
Counting loopfor (int i = 0; i < n; i++) { }
Loop over a list or arrayfor (String name : names) { }
while and do-whilewhile (it.hasNext()) { } do { } while (retry());do-while always runs at least once
Skip to the next round, or stopcontinue; break;
Break out of an outer loopouter: 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 lambdanames.forEach(System.out::println);

Classes and objects

TaskCodeNotes
Define a classpublic class Account { private double balance; }One public class per file, and the file is named after it
Constructorpublic Account(String owner) { this.owner = owner; }No return type. this.owner is the field
One constructor calling anotherpublic Account() { this("anonymous"); }
Create an objectAccount a = new Account("Ada"); var b = new Account("Bob");
Methodpublic void deposit(double amount) { balance += amount; }
Static methodstatic int add(int a, int b) { return a + b; }Called on the class: Account.add(1, 2)
Shared by every objectprivate static int count = 0;
Who can see itpublic protected privateNo keyword means the same package only
Inheritpublic class Savings extends Account { }One superclass only
Call the parent constructorpublic 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 versionsuper.toString()
Class that must be extendedabstract class Shape { abstract double area(); }Cannot be created with new
Stop subclassing or overridingfinal 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 valuesenum Size { SMALL, MEDIUM, LARGE }Compare with ==
Enum with fieldsenum Coin { PENNY(1), DIME(10); final int cents; Coin(int cents) { this.cents = cents; } }
Enum helpersSize.valueOf("SMALL") Size.values() s.name() s.ordinal()
Class inside a classstatic class Node { int value; Node next; }Leave out static and each Node holds a hidden link to the outer object
One-off implementationRunnable r = new Runnable() { public void run() { go(); } };An anonymous class. A lambda is shorter
Program entry pointpublic static void main(String[] args) { }
Hello world, short formvoid main() { IO.println("Hello"); }Java 25. A whole file with no class around it

Interfaces and records

TaskCodeNotes
Define an interfaceinterface Shape { double area(); }Its methods are public and abstract unless they have a body
Implement itclass Circle implements Shape { public double area() { return Math.PI * r * r; } }A class can implement several, comma separated
Method with a bodydefault String describe() { return "area " + area(); }Implementations get it for free and may override it
Static helperstatic Shape unit() { return new Circle(1); }Called as Shape.unit()
Constantint 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 linerecord Point(int x, int y) { }Final fields, constructor, accessors, equals, hashCode and toString
Read a fieldp.x()The accessor is x(), not getX()
Validate in the constructorrecord 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 recordrecord Point(int x, int y) { static Point origin() { return new Point(0, 0); } double length() { return Math.hypot(x, y); } }
Change one fieldPoint moved = new Point(p.x(), p.y() + 1);Records cannot change, so make a new one
Only these types may implement itsealed interface Shape permits Circle, Square { }Each must be final, sealed or non-sealed. Records are final already
switch with no defaultdouble 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

TaskCodeNotes
Generic classclass Box<T> { private T value; T get() { return value; } }
Use itBox<String> box = new Box<>();The empty <> (diamond) copies the type from the left
Generic methodstatic <T> T firstOf(List<T> list) { return list.get(0); }The <T> goes before the return type
Two type parametersrecord Pair<A, B>(A first, B second) { }
Generic interfaceinterface Repository<T, ID> { Optional<T> findById(ID id); }
Only accept certain typesstatic <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 subtypestatic double sum(List<? extends Number> nums)Takes List<Integer> or List<Double>. You cannot add to it
Write into a list of any supertypestatic void fill(List<? super Integer> out)Takes List<Integer>, List<Number> or List<Object>
Any type at allstatic void printAll(List<?> items)
No primitivesList<Integer> nums;List<int> does not compile
Make a T when you need onestatic <T> List<T> fresh(Supplier<T> make, int n)new T() does not compile. Pass a Supplier or a Class<T>
Raw typeList 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.

TaskCodeNotes
Catchtry { risky(); } catch (IOException e) { System.err.println(e.getMessage()); }
Catch specific types firstcatch (FileNotFoundException e) { } catch (IOException e) { }The compiler rejects them in the wrong order
Catch several the same waycatch (NumberFormatException | ArithmeticException e) { }
Always runfinally { cleanup(); }Runs whether the try succeeded, threw or returned
Close a resource for youtry (var reader = Files.newBufferedReader(path)) { }Closed however the block ends. Works for anything AutoCloseable
Several resourcestry (var in = Files.newInputStream(src); var out = Files.newOutputStream(dst)) { in.transferTo(out); }Closed in reverse order
Throwthrow new IllegalArgumentException("age must be positive");
Declare a checked exceptionvoid load() throws IOException { }
Your own checked exceptionclass OutOfStockException extends Exception { OutOfStockException(String msg) { super(msg); } }
Your own unchecked exceptionclass ConfigException extends RuntimeException { }
Wrap one and keep the causethrow new IllegalStateException("could not load config", e);Pass e on, or its stack trace is lost
Checked IOException as uncheckedthrow new UncheckedIOException(e);Useful inside a lambda, which cannot throw checked exceptions
Print the stack tracee.printStackTrace();
Ignore the exception objectcatch (NumberFormatException _) { }Java 22
Check an assumptionassert n > 0 : "n must be positive";Only runs with java -ea
Common unchecked exceptionsNullPointerException IllegalArgumentException IllegalStateException IndexOutOfBoundsExceptionThrow 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.

TaskCodeNotes
No argumentsRunnable hello = () -> System.out.println("hi");
One argumentFunction<Integer, Integer> twice = x -> x * 2;
Several argumentsBinaryOperator<Integer> add = (a, b) -> a + b;
Several statementsFunction<String, Integer> score = s -> { int n = s.length(); return n * n; };
Call ittwice.apply(5) hello.run()The method name comes from the interface
Refer to a static methodFunction<String, Integer> parse = Integer::parseInt;
Refer to a method of one objectConsumer<String> print = System.out::println;
Refer to a method of whatever is passedFunction<String, Integer> len = String::length;
Refer to a constructorSupplier<List<String>> make = ArrayList::new;
Takes a value, returns a valueFunction<T, R> BiFunction<T, U, R>apply
Takes nothing, returns a valueSupplier<T>get
Takes a value, returns nothingConsumer<T> BiConsumer<T, U>accept
Takes a value, returns a booleanPredicate<T>test
Same type in and outUnaryOperator<T> BinaryOperator<T>
Avoid boxing intsIntPredicate IntFunction<R> ToIntFunction<T> IntUnaryOperatorAlso Long and Double versions
Chain two functionstwice.andThen(x -> x + 1).apply(3)7. compose runs the other one first
Combine conditionsisLong.and(isUpper) isLong.negate() Predicate.not(String::isBlank)
Use a local variable insideint 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.

TaskCodeNotes
From a listnames.stream()
From valuesStream.of("a", "b", "c")
From an arrayArrays.stream(nums)
Numbers in a rangeIntStream.range(0, 10)0 to 9. rangeClosed(1, 10) includes 10
Keep matchesnames.stream().filter(n -> n.length() > 3)
Transform each onenames.stream().map(String::toUpperCase)
Collect into a listList<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
GroupMap<String, List<Person>> byCity = people.stream().collect(Collectors.groupingBy(Person::city));
Count each groupCollectors.groupingBy(Person::city, Collectors.counting())Gives Map<String, Long>
Split into true and falseCollectors.partitioningBy(n -> n > 0)
Join into one stringCollectors.joining(", ", "[", "]")Separator, prefix, suffix
Sort.sorted() .sorted(Comparator.comparing(Person::age))
Drop duplicates, take some, skip some.distinct() .limit(10) .skip(5)
Sum and averagepeople.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 sequenceStream.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 objectsIntStream.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.

TaskCodeNotes
Return a valuereturn Optional.of(user);Throws NullPointerException if user is null
Return a value that might be nullreturn Optional.ofNullable(cache.get(id));
Return nothingreturn 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

TaskCodeNotes
Today's dateLocalDate today = LocalDate.now();java.time. Never java.util.Date in new code
A particular dateLocalDate.of(2026, 9, 27)Months are 1 to 12
Date arithmetictoday.plusDays(30) today.minusMonths(1)Returns a new date
Days between two datesChronoUnit.DAYS.between(start, end)
Parse and format a dateLocalDate.parse("2026-09-27") today.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
A moment in timeInstant.now()UTC. For timestamps
Date and time in a zoneZonedDateTime.now(ZoneId.of("Europe/London"))
A length of timeDuration.ofMinutes(90).toHours()1. Duration.between(a, b) measures one
Read a whole fileString text = Files.readString(Path.of("notes.txt"));java.nio.file. Throws IOException
Read the linesList<String> lines = Files.readAllLines(path);
Stream a big file line by linetry (Stream<String> lines = Files.lines(path)) { }Close it, which try does for you
Write a fileFiles.writeString(path, text);Replaces it. Add StandardOpenOption.APPEND to add to the end
Check and createFiles.exists(path) Files.createDirectories(dir)
List a foldertry (Stream<Path> files = Files.list(dir)) { }
Read from the keyboardString name = IO.readln("Name: ");Java 25. new Scanner(System.in).nextLine() before that
Random numberint roll = ThreadLocalRandom.current().nextInt(1, 7);1 to 6. The upper bound is excluded
MathsMath.max(a, b) Math.abs(x) Math.pow(2, 10) Math.sqrt(x)
RoundMath.round(2.5)3, but Math.round(-2.5) is -2. It rounds half up
Keep a number within boundsMath.clamp(value, 0, 100)Java 21
Match a regexMatcher m = Pattern.compile("(\\w+)@(\\w+)").matcher(email); if (m.matches()) { m.group(1); }java.util.regex. find() for a match anywhere
Replace with a regexs.replaceAll("\\s+", " ")
Fail fast on nullthis.name = Objects.requireNonNull(name, "name");
Unique IDUUID.randomUUID().toString()
Environment variableSystem.getenv("HOME")null if it is not set
Time somethinglong start = System.nanoTime();For measuring. currentTimeMillis is for the date
HTTP GETHttpResponse<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 taskstry (var pool = Executors.newVirtualThreadPerTaskExecutor()) { pool.submit(() -> fetch(url)); }Java 21. Virtual threads. Waits for every task at the closing brace
Thread-safe map and counternew ConcurrentHashMap<String, Integer>() new AtomicInteger().incrementAndGet()java.util.concurrent
Import a whole moduleimport 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.java

Sorting 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 rightWhat actually happensDo 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 == bfalse. Only -128 to 127 are cached, so small numbers happen to passa.equals(b)
nums.remove(3) on a List<Integer>Removes the item at index 3, not the value 3nums.remove(Integer.valueOf(3))
List.of(1, 2).add(3)UnsupportedOperationException: List.of is unmodifiablenew ArrayList<>(List.of(1, 2))
Arrays.asList(arr).add(x)UnsupportedOperationException: the list is a fixed-size view of the arrayCopy it into a new ArrayList<>(...)
Removing from a list inside for (String s : list)ConcurrentModificationExceptionlist.removeIf(...)
int avg = total / count; then (double) itThe division already happened in whole numbers(double) total / count
s.toUpperCase(); on its own lineStrings never change, so the result is thrown aways = s.toUpperCase();
"1.2.3".split(".")An empty array: the argument is a regex and . matches everythingsplit("\\.")
int n = map.get(key); for a missing keyget returns null, and unboxing null throws NullPointerExceptionmap.getOrDefault(key, 0)
Overriding equals but not hashCodeEqual objects land in different buckets, so HashSet and HashMap lose themOverride both, or use a record
optional.get()NoSuchElementException when it is emptyorElse, orElseThrow or map
Reusing a stream after .toList()IllegalStateException: a stream runs onceCall .stream() again
Collectors.toMap(...) with a repeated keyIllegalStateException: Duplicate keyPass a merge function: toMap(k, v, (a, b) -> a)
new BigDecimal(0.1)0.1000000000000000055511151231257827..., the double's exact valuenew 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.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.