Java cheatsheet
Learn Java step by step, from your first program to running tasks side by side. Each section has three parts:
- In short β the idea in one sentence.
- Example β code with a comment on every line saying what it produces.
- Try it β a tiny exercise to check you understood.
Want the longer story behind a topic? The complete guide walks through it in more depth.
π Full guide: Java β BeginnerGo through Part 1 in order β each section builds on the one before. Once
you're comfortable, move to Part 2. Part 3 is for when you're writing
larger programs. Type the examples yourself instead of copying them; you'll
remember them far better. You can try any snippet in jshell, the Java
playground that comes with the JDK (Java Development Kit).
Contentsβ
Part 1 β Beginner: Your first program Β· Variables & types Β· Text & numbers Β· Making decisions & loops Β· Arrays & lists Β· Methods Β· Handling errors Β· Reading & writing files
Part 2 β Core: Classes & objects Β· Records Β· Inheritance & interfaces Β· equals, hashCode & toString Β· Sets, maps & queues Β· Generics Β· Lambdas Β· Streams Β· Optional Β· Build tools Β· Testing with JUnit Β· Common mistakes
Part 3 β Advanced: Enums & switch patterns Β· Immutability Β· Threads & thread pools Β· Sharing data safely Β· CompletableFuture Β· The JVM & memory Β· Words you'll meet
Part 1 β Beginnerβ
1. Your first programβ
In short: Java code lives inside a class, and a program starts at a
method called main.
public class Hello { // file must be named Hello.java
public static void main(String[] args) { // where the program starts
System.out.println("Hello, World!"); // shows: Hello, World!
}
}
java Hello.java # compile and run in one step (Java 11+)
javac Hello.java # or: compile to Hello.class (bytecode)...
java Hello # ...then run it
Java is compiled: javac turns your code into bytecode, and the JVM
(Java Virtual Machine) runs that bytecode on any operating system. Every
statement ends with ;, and { } braces group code together.
Try it: change the program to print your name on one line and your city on the next.
2. Variables & typesβ
In short: every variable has a type that is fixed when you declare it β the compiler stops you putting the wrong kind of value in.
int age = 30; // int β whole number
double price = 9.99; // double β decimal number
boolean active = true; // boolean β true or false
char grade = 'A'; // char β one character, single quotes
String name = "Ada"; // String β text, double quotes
long views = 3_000_000L; // long β big whole number (L at the end)
var city = "London"; // var β Java works out the type (String) for you
final int MAX = 5; // final β can't be changed after this line
int, double, boolean, char, long are primitives β plain values,
lower-case names. String and everything else with a capital letter is an
object β it can be null ("points at nothing") and has methods like
name.length().
Try it: declare your age and height, then print
"Age: 30, height: 1.75" using + to join the text and values.
3. Text & numbersβ
In short: String has many helper methods; numbers work like a calculator,
except that int / int drops the decimals.
String text = "Java";
text.length(); // 4
text.charAt(0); // 'J' β first character (counting starts at 0)
text.substring(1, 3); // "av" β characters 1 and 2
text.toUpperCase(); // "JAVA"
text.contains("av"); // true
"a,b,c".split(","); // String[] {"a", "b", "c"}
String.format("%s is %d", text, 30); // "Java is 30"
7 / 2; // 3 β int / int drops the decimal part
7 / 2.0; // 3.5 β one double makes the answer a double
7 % 2; // 1 β the remainder
Math.max(3, 8); // 8
Math.pow(2, 3); // 8.0 β always a double
Integer.parseInt("42"); // 42 β text to int
String.valueOf(42); // "42" β int to text
Try it: with String name = "ada lovelace";, print only the first name in
capital letters. (Hint: split(" ") then toUpperCase().)
4. Making decisions & loopsβ
In short: if chooses what to run; switch picks one of many; for and
while repeat.
int score = 72;
if (score >= 90) {
System.out.println("A");
} else if (score >= 70) {
System.out.println("B"); // shows: B
} else {
System.out.println("C");
}
String day = "SAT";
String kind = switch (day) { // switch expression (Java 14+)
case "SAT", "SUN" -> "weekend";
default -> "weekday";
}; // kind is "weekend"
for (int i = 0; i < 3; i++) { // i = 0, 1, 2
System.out.println(i);
}
int[] nums = {4, 5, 6};
for (int n : nums) { // "for each n in nums"
System.out.println(n); // 4, then 5, then 6
}
int count = 3;
while (count > 0) { // repeat while the condition is true
count--; // count = count - 1
}
&& means "and", || means "or", ! means "not". Use break to leave a
loop early and continue to skip to the next round.
Try it: print the numbers 1 to 15, but print "Fizz" instead of any
number divisible by 3.
5. Arrays & listsβ
In short: an array has a fixed size; an ArrayList grows and
shrinks. In day-to-day code you'll mostly use lists.
import java.util.ArrayList;
import java.util.List;
int[] scores = {90, 75, 60}; // int[] β array, size fixed at 3
scores[0]; // 90 β first item
scores.length; // 3 β no brackets for arrays
List<String> names = new ArrayList<>(); // ArrayList β empty, can grow
names.add("Ada"); // ["Ada"]
names.add("Linus"); // ["Ada", "Linus"]
names.get(1); // "Linus"
names.size(); // 2 β brackets for lists
names.contains("Ada"); // true
names.remove("Ada"); // ["Linus"]
List<Integer> fixed = List.of(1, 2, 3); // List β can't be changed (add throws)
List<String> reads "a list of Strings". The <> part says what type the
list holds. Lists hold objects only, so use Integer (not int) and Double
(not double) inside < >.
Try it: make an ArrayList of three fruits, add a fourth, then print
the list and its size.
6. Methodsβ
In short: a method is a named, reusable block of code. You say the type of each input and the type of what it gives back.
public class MathTools {
static int add(int a, int b) { // takes two ints, returns an int
return a + b;
}
static double add(double a, double b) { // same name, different inputs
return a + b; // ("overloading")
}
static void greet(String name) { // void β returns nothing
System.out.println("Hi " + name);
}
public static void main(String[] args) {
add(2, 3); // 5
add(1.5, 2.0); // 3.5 β picks the double version
greet("Ada"); // shows: Hi Ada
}
}
static means the method belongs to the class itself, so you can call it
without making an object first. Java has no default parameter values β use
overloading instead.
Try it: write average(int[] numbers) that returns the average as a
double.
7. Handling errorsβ
In short: when something goes wrong, Java throws an exception.
try / catch lets you handle it instead of crashing.
try {
int n = Integer.parseInt("abc"); // throws NumberFormatException
} catch (NumberFormatException e) {
System.out.println("Not a number: " + e.getMessage());
} finally {
System.out.println("always runs"); // runs whether or not it failed
}
static void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("age must be >= 0"); // raise your own
}
}
Two kinds of exception:
| Kind | Example | Rule |
|---|---|---|
| Checked | IOException | The compiler forces you to catch it or add throws IOException to the method |
| Unchecked | NullPointerException, IllegalArgumentException | Subclasses of RuntimeException; usually a bug to fix, not to catch |
Try it: read a number with Integer.parseInt, and print "try again" if
the text isn't a number.
8. Reading & writing filesβ
In short: the Files and Path classes read and write whole files in one
line; try (...) closes things for you.
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Path path = Path.of("notes.txt");
Files.writeString(path, "line 1\nline 2\n"); // write text (overwrites)
String all = Files.readString(path); // String β the whole file
List<String> lines = Files.readAllLines(path); // List<String> β ["line 1", "line 2"]
Files.exists(path); // true
try (BufferedReader reader = Files.newBufferedReader(path)) { // try-with-resources
String line;
while ((line = reader.readLine()) != null) { // one line at a time β good for big files
System.out.println(line);
}
} // reader is closed automatically here
These methods throw IOException (a checked exception), so the method that
calls them needs throws IOException or a try / catch.
Try it: write three names to a file, one per line, then read the file back and print how many lines it has.
Part 2 β Coreβ
9. Classes & objectsβ
In short: a class is a blueprint; an object is one thing made from it, with its own data (fields) and behaviour (methods).
public class BankAccount {
private final String owner; // private β only this class can touch it
private double balance; // each object has its own balance
public BankAccount(String owner) { // constructor β runs on "new"
this.owner = owner; // this.owner = the field, owner = the input
this.balance = 0;
}
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("amount must be > 0");
balance += amount;
}
public double getBalance() { // "getter" β read-only access from outside
return balance;
}
}
BankAccount acc = new BankAccount("Ada"); // BankAccount β a new object
acc.deposit(50);
acc.getBalance(); // 50.0
Keeping fields private and changing them only through methods is called
encapsulation β the class can check every change (like amount > 0).
Try it: add a withdraw(double amount) method that refuses to let the
balance go below zero.
10. Recordsβ
In short: a record (Java 16+) is a one-line class for plain data β Java
writes the constructor, getters, equals, hashCode, and toString for you.
public record User(String name, int age) { }
User u = new User("Ada", 36); // User β a record
u.name(); // "Ada" β getter has no "get" prefix
u.age(); // 36
u; // User[name=Ada, age=36] β free toString
u.equals(new User("Ada", 36)); // true β compares the values
public record Email(String value) {
public Email { // "compact constructor" β checks input
if (!value.contains("@")) throw new IllegalArgumentException("bad email");
}
}
Records can't be changed after they're made (their fields are final). Use
one whenever a class just carries data β test data, API responses, config.
Try it: make a record Point(int x, int y) and add a method
distanceFromOrigin() that returns a double.
11. Inheritance & interfacesβ
In short: extends reuses a parent class; implements promises a set of
methods. A class can extend one class but implement many interfaces.
interface Shape { // interface β a promise of methods
double area(); // no body: each class writes its own
}
abstract class Animal { // abstract β can't be made with "new"
protected final String name; // protected β this class and its children
Animal(String name) { this.name = name; }
abstract String sound(); // children must write this
String speak() { return name + " says " + sound(); }
}
class Dog extends Animal { // Dog is an Animal
Dog(String name) { super(name); } // super(...) β call the parent's constructor
@Override String sound() { return "woof"; }
}
record Circle(double r) implements Shape {
public double area() { return Math.PI * r * r; }
}
new Dog("Rex").speak(); // "Rex says woof"
Shape s = new Circle(1); // hold it by its interface type
s.area(); // 3.14159...
@Override asks the compiler to check that you really are replacing a parent
method β it catches typos. Prefer interfaces for "can do" (Comparable,
Runnable) and small class trees over deep ones.
Try it: add a record Square(double side) implements Shape and put a
circle and a square in one List<Shape>, then print each area.
12. equals, hashCode & toStringβ
In short: == asks "same object?"; equals asks "same value?". If you
write equals, you must also write hashCode, or sets and maps break.
String a = new String("hi");
String b = new String("hi");
a == b; // false β two different objects
a.equals(b); // true β same text
import java.util.Objects;
class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
return o instanceof Point p && p.x == x && p.y == y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
@Override public String toString() { return "Point(" + x + ", " + y + ")"; }
}
hashCode is a number that HashSet and HashMap use to find a bucket
quickly; equal objects must give the same number. A record writes all three
for you β another reason to use one.
Try it: put two new Point(1, 2) objects in a HashSet. What's the size
with hashCode, and what is it if you delete hashCode?
13. Sets, maps & queuesβ
In short: pick the collection by the question you'll ask it most often.
import java.util.*;
Set<String> tags = new HashSet<>(List.of("a", "b", "a")); // HashSet β {"a", "b"}, no duplicates
tags.contains("a"); // true, and fast at any size
Map<String, Integer> ages = new HashMap<>(); // HashMap β key β value
ages.put("Ada", 36); // {Ada=36}
ages.get("Ada"); // 36
ages.get("Bob"); // null β missing key
ages.getOrDefault("Bob", 0); // 0
ages.merge("Ada", 1, Integer::sum); // {Ada=37} β add 1 to the existing value
for (Map.Entry<String, Integer> e : ages.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
Deque<String> stack = new ArrayDeque<>(); // ArrayDeque ("deck") β stack or queue
stack.push("a"); stack.push("b"); // stack: b on top
stack.pop(); // "b" β last in, first out
Queue<String> queue = new ArrayDeque<>();
queue.offer("a"); queue.offer("b");
queue.poll(); // "a" β first in, first out
| You need⦠| Use | Why |
|---|---|---|
| Items in order, by position | ArrayList | Fast get(i) |
| "Have I seen this?" | HashSet | Fast contains |
| Look up a value by key | HashMap | Fast get(key) |
| Keep keys sorted | TreeMap / TreeSet | Sorted, a bit slower |
| Keep insertion order | LinkedHashMap / LinkedHashSet | Predictable printing |
| Stack or queue | ArrayDeque | Fast at both ends |
Try it: count how many times each word appears in
"to be or not to be" using a HashMap<String, Integer>.
14. Genericsβ
In short: generics let one class or method work with any type while the
compiler still checks you use it correctly. T is a placeholder for "some type".
class Box<T> { // Box of some type T
private final T value;
Box(T value) { this.value = value; }
T get() { return value; }
}
Box<String> b = new Box<>("hi"); // Box<String>
String s = b.get(); // no cast needed
static <T extends Comparable<T>> T largest(List<T> items) { // T must be comparable
T best = items.get(0);
for (T item : items) if (item.compareTo(best) > 0) best = item;
return best;
}
largest(List.of(3, 9, 4)); // 9
largest(List.of("pear", "apple")); // "pear"
double sum(List<? extends Number> nums) { // ? extends β read Numbers out
double total = 0;
for (Number n : nums) total += n.doubleValue();
return total;
}
<? extends Number> means "a list of some kind of Number" β good for
reading. <? super Integer> means "a list you can put Integers into" β good
for writing. Memory aid: PECS β Producer extends, Consumer super.
Try it: write a generic record Pair<A, B>(A first, B second) and make a
Pair<String, Integer>.
15. Lambdas & method referencesβ
In short: a lambda is a small unnamed function you can pass around,
like x -> x * 2.
import java.util.function.*;
Function<Integer, Integer> twice = x -> x * 2; // takes Integer, returns Integer
twice.apply(5); // 10
Predicate<String> isEmpty = s -> s.isEmpty(); // takes a value, returns boolean
Supplier<Double> dice = () -> Math.random(); // takes nothing, returns a value
Consumer<String> show = s -> System.out.println(s);// takes a value, returns nothing
List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Bo"));
names.sort((x, y) -> x.length() - y.length()); // ["Bo", "Ada", "Linus"]
names.sort(Comparator.naturalOrder()); // ["Ada", "Bo", "Linus"]
names.forEach(System.out::println); // method reference β same as s -> System.out.println(s)
A lambda fits anywhere Java expects a functional interface β an interface
with exactly one method (Function, Predicate, Runnable, Comparatorβ¦).
Try it: sort a list of words by their last letter using a lambda.
16. Streamsβ
In short: a stream runs a list of items through steps β filter, change, collect β without writing a loop.
import java.util.stream.*;
record User(String name, int age) { }
List<User> users = List.of(new User("Ada", 36), new User("Bo", 15), new User("Cy", 22));
List<String> adults = users.stream()
.filter(u -> u.age() >= 18) // keep only adults
.map(User::name) // User β name
.sorted() // A to Z
.toList(); // List<String> β ["Ada", "Cy"] (Java 16+)
int totalAge = users.stream().mapToInt(User::age).sum(); // 73
boolean anyTeen = users.stream().anyMatch(u -> u.age() < 18); // true
Map<Boolean, List<User>> byAdult = users.stream()
.collect(Collectors.partitioningBy(u -> u.age() >= 18)); // {false=[Bo], true=[Ada, Cy]}
String csv = Stream.of("a", "b", "c").collect(Collectors.joining(",")); // "a,b,c"
IntStream.rangeClosed(1, 5).sum(); // 15
Nothing runs until the last step (toList, sum, anyMatchβ¦). A stream can
be used only once β make a new one each time.
Try it: from a list of words, build a list of only the words longer than 4 letters, in capitals.
17. Optionalβ
In short: Optional is a box that may or may not hold a value β a clear
way to say "there might be no answer" instead of returning null.
import java.util.Optional;
Optional<String> found = Optional.of("Ada"); // Optional holding "Ada"
Optional<String> none = Optional.empty(); // empty Optional
found.isPresent(); // true
none.orElse("guest"); // "guest" β fallback value
found.map(String::toUpperCase); // Optional["ADA"]
found.ifPresent(System.out::println);// shows: Ada
Optional<String> first = List.of("x", "yy").stream()
.filter(s -> s.length() > 1)
.findFirst(); // Optional["yy"]
Use Optional as a return type. Don't use it for fields or parameters, and
avoid .get() β it throws if the box is empty; prefer orElse or
orElseThrow().
Try it: write findUser(String name) that returns Optional<String> and
prints "not found" via orElse when the name is missing.
18. Build toolsβ
In short: Maven or Gradle downloads libraries, compiles, and runs tests with one command. Both use the same folder layout.
my-app/
βββ pom.xml β Maven settings (or build.gradle for Gradle)
βββ src/
βββ main/java/ β your code
βββ test/java/ β your tests
<!-- pom.xml: add a library ("dependency") -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope> <!-- only needed for tests -->
</dependency>
mvn clean test # Maven: delete old output, compile, run tests
mvn package # build a .jar file in target/
./gradlew test # Gradle: run tests (the wrapper script pins the version)
./gradlew build # compile, test, and package
A JAR (Java ARchive) is a zip of compiled classes β the file you ship or
run with java -jar app.jar.
Try it: create a Maven project, add the JUnit dependency above, and run
mvn test.
19. Testing with JUnitβ
In short: a test is a method that checks your code gives the right answer.
JUnit 5 finds methods marked @Test and runs them.
// src/test/java/CalculatorTest.java
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void addsTwoNumbers() {
assertEquals(5, 2 + 3); // passes if equal
}
@Test
void rejectsBadInput() {
assertThrows(NumberFormatException.class,
() -> Integer.parseInt("abc")); // passes if it throws
}
@ParameterizedTest // one test, many inputs
@CsvSource({"1, 1, 2", "2, 3, 5", "-1, 1, 0"})
void adds(int a, int b, int expected) {
assertEquals(expected, a + b);
}
@BeforeEach
void setUp() { /* runs before every test */ }
}
Run with mvn test or ./gradlew test. Name tests after the behaviour they
check (rejectsBadInput), so a failure explains itself.
Try it: write a test for the average method you made in section 6.
20. Common mistakesβ
In short: the classic Java traps, and how to avoid each one.
// 1. Comparing text with ==
"hi" == new String("hi"); // false β different objects
"hi".equals(new String("hi")); // true β always compare Strings with equals
// 2. Whole-number division
double half = 1 / 2; // 0.0 β int / int happens first
double ok = 1 / 2.0; // 0.5
// 3. Calling a method on null β NullPointerException
String name = null;
// name.length(); // crashes
"Ada".equals(name); // false β safe: the known value goes first
// 4. Removing from a list while looping over it
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
// for (int n : nums) if (n % 2 == 0) nums.remove((Integer) n); // ConcurrentModificationException
nums.removeIf(n -> n % 2 == 0); // [1, 3] β the safe way
// 5. Comparing wrapper numbers with ==
Integer a = 1000, b = 1000;
a == b; // false β two objects (only -128..127 are shared)
a.equals(b); // true
// 6. List.of can't be changed
List<String> fixed = List.of("a");
// fixed.add("b"); // UnsupportedOperationException
Try it: run each commented-out line in jshell and read the error message
it prints β you'll recognise them instantly next time.
Part 3 β Advancedβ
21. Enums & switch patternsβ
In short: an enum is a fixed list of named values; modern switch can
also match on an object's type and pull its parts out.
enum Status { PASS, FAIL, SKIP }
Status s = Status.valueOf("FAIL"); // Status.FAIL β from text
Status.values(); // [PASS, FAIL, SKIP]
String icon = switch (s) { // compiler checks every value is handled
case PASS -> "β";
case FAIL -> "β";
case SKIP -> "-";
}; // "β"
sealed interface Shape permits Circle, Square { } // sealed β only these two may implement it
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
static double area(Shape shape) {
return switch (shape) { // pattern matching (Java 21+)
case Circle c -> Math.PI * c.r() * c.r();
case Square(double side) -> side * side; // "record pattern" β unpacks the record
}; // no default needed: sealed = all cases known
}
Try it: add a Triangle(double base, double height) record to Shape and
see the compiler tell you area is missing a case.
22. Immutabilityβ
In short: an immutable object can't change after it's made, so it's safe to share between methods and threads.
final List<String> a = new ArrayList<>();
a.add("x"); // allowed β final stops re-assigning a, not changing it
// a = new ArrayList<>(); // not allowed
List<String> copy = List.copyOf(a); // immutable copy β add/remove throw
Map<String, Integer> m = Map.of("a", 1, "b", 2); // immutable map
record Money(long cents, String currency) { // records are immutable
Money plus(Money other) {
return new Money(cents + other.cents, currency); // return a NEW object
}
}
String is immutable too: s.toUpperCase() returns a new string and leaves
s alone. When building text in a loop, use StringBuilder to avoid making
thousands of throwaway strings.
Try it: build the text "0,1,2,β¦,9" with a StringBuilder in a loop.
23. Threads & thread poolsβ
In short: a thread runs code at the same time as other code. Don't
create threads by hand β hand tasks to an ExecutorService (a thread
pool) and let it manage them.
import java.util.concurrent.*;
try (ExecutorService pool = Executors.newFixedThreadPool(4)) { // 4 worker threads; closes itself
Future<Integer> f = pool.submit(() -> 6 * 7); // Future β a result that will arrive later
f.get(); // 42 β waits for the answer
}
try (ExecutorService vpool = Executors.newVirtualThreadPerTaskExecutor()) { // Java 21+
for (int i = 0; i < 1_000; i++) {
int id = i;
vpool.submit(() -> { // virtual threads are cheap β fine for 1000s of waits
Thread.sleep(100); // stands in for a slow network call
return id;
});
}
}
Virtual threads (Java 21+) are lightweight threads managed by the JVM β ideal when tasks spend most of their time waiting on the network or disk. For number-crunching, a fixed pool about the size of your CPU count is best.
Try it: submit three tasks that each sleep one second and return a number; time how long collecting all three answers takes.
24. Sharing data safelyβ
In short: when two threads change the same data at once, updates get lost. Use thread-safe types instead of plain ones.
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
int unsafe = 0; // unsafe++ from many threads loses updates ("race condition")
AtomicInteger hits = new AtomicInteger(); // thread-safe counter
hits.incrementAndGet(); // 1
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>(); // thread-safe map
counts.merge("page", 1, Integer::sum); // {page=1}
Object lock = new Object();
synchronized (lock) { // only one thread at a time runs this block
// update shared state here
}
A race condition is a bug where the result depends on which thread runs
first. Rule of thumb: share as little as possible, prefer immutable data, and
reach for Atomic* and Concurrent* types before synchronized.
Try it: start 1,000 tasks that each add 1 to a plain int[] counter, and
1,000 that add to an AtomicInteger. Compare the totals.
25. CompletableFutureβ
In short: CompletableFuture chains steps that run in the background β
"when this finishes, do that" β without blocking while you wait.
import java.util.concurrent.CompletableFuture;
CompletableFuture<String> user = CompletableFuture.supplyAsync(() -> "Ada"); // runs in background
CompletableFuture<Integer> score = CompletableFuture.supplyAsync(() -> 90);
String report = user
.thenCombine(score, (u, s) -> u + " scored " + s) // when both are done
.exceptionally(err -> "failed: " + err.getMessage()) // if anything threw
.join(); // "Ada scored 90" β wait for the result
CompletableFuture.allOf(user, score).join(); // wait for several at once
Use it to call several services at the same time and combine the answers. On Java 21+, plain blocking code on virtual threads is often simpler.
Try it: fetch three numbers with supplyAsync, then print their sum once
all three are ready.
26. The JVM & memoryβ
In short: the JVM runs your bytecode, stores objects on the heap, and frees unused objects automatically with the garbage collector.
| Part | What it holds |
|---|---|
| Stack | Each method call's local variables β one stack per thread, cleared when the method returns |
| Heap | Every object made with new β shared by all threads |
| Garbage collector (GC) | Finds objects nothing points to any more and frees their memory |
| JIT compiler | "Just-in-time" β turns frequently run bytecode into fast machine code while the program runs |
java -Xms512m -Xmx2g -jar app.jar # start with 512 MB heap, allow up to 2 GB
jcmd <pid> GC.heap_info # how full is the heap of a running program?
An OutOfMemoryError means the heap is full β usually a collection that keeps
growing (a cache with no limit, a list you never clear). Measure before
tuning: most slow Java code is slow because of the algorithm or the database,
not the JVM.
27. Words you'll meetβ
In short: the jargon, in one line each.
| Word | Meaning |
|---|---|
| JDK | Java Development Kit β the compiler (javac), java, jshell, and tools. Install this. |
| JVM | Java Virtual Machine β runs compiled bytecode on any operating system |
| Bytecode | The .class files javac produces; the same file runs on Windows, Mac, Linux |
| LTS | Long-Term Support β Java 17, 21, 25 get years of fixes; use one of these |
| Primitive | A plain value type: int, double, boolean, char, long⦠|
| Wrapper | The object version of a primitive: Integer, Double, Boolean⦠|
| Autoboxing | Java converting int β Integer for you automatically |
| NPE | NullPointerException β you called a method on null |
| POJO | "Plain Old Java Object" β a simple class with fields, getters, setters |
| Annotation | A @Label like @Test or @Override that tools and frameworks read |
| Classpath | The list of folders and JARs where Java looks for classes |
| Artifact | A built file (usually a JAR) identified by groupId:artifactId:version in Maven |
For role-specific examples and longer explanations, see the complete guide and Java for SDET.