The crash that only happened to new students
Riya's canteen app greets students by name: findStudent(roll).getName(). It works for everyone already registered. On admission day, the app crashes for every new student, because findStudent returns null for them.
Sir, I can add if (s != null) everywhere. But how will I remember every place?
You won't. Nobody does. Make the method return Optional<Student>. Then the type itself tells every caller: “this might be empty, handle it”.
And handling it means more ifs?
No: map, orElse, orElseThrow. Let's look inside the box.
What is Optional?
java.util.Optional<T> is a container object that may or may not contain a non-null value of type T. It is intended mainly as a method return type where there may be no result, to make the absence explicit and avoid returning null. An Optional is created with Optional.of(value) (value must be non-null), Optional.ofNullable(value) or Optional.empty(). Its value is accessed through methods such as orElse, orElseGet, orElseThrow and ifPresent, and transformed with map, filter and flatMap, which return Optionals. Calling get() on an empty Optional throws NoSuchElementException.
Optional is a gift box that might be empty. You're not allowed to assume there's something inside; you say what to do in both cases.
Words you'll meet in this lesson
orElse("Guest").orElseGet(supplier) computes it only when needed.orElseThrow: stop with a clear error when a value is required.null vs Optional
Returning null | Returning Optional | |
|---|---|---|
| Does the signature warn callers? | No | Yes: Optional<Student> |
| Forgetting to check | NullPointerException later | Code doesn't compile without unwrapping |
| Default value | s != null ? s.name : "Guest" | .map(Student::name).orElse("Guest") |
| Chaining lookups | Nested null checks | map / flatMap chain |
| Cost | None | One small object per result |
Exam tip: “Optional is a container that may or may not hold a non-null value, introduced in Java 8 to represent optional return values and reduce NullPointerExceptions. of throws on null; ofNullable doesn't. orElse always evaluates its argument; orElseGet evaluates only when empty.”
The Optional toolkit
// create
Optional.of(value) // value must not be null
Optional.ofNullable(maybe) // null becomes empty
Optional.empty()
// check and unwrap
opt.isPresent() opt.isEmpty()
opt.orElse(fallback) opt.orElseGet(() -> compute())
opt.orElseThrow() opt.orElseThrow(() -> new XyzException("..."))
opt.ifPresent(v -> ...) opt.ifPresentOrElse(v -> ..., () -> ...)
// transform (return Optionals)
opt.map(Function) opt.filter(Predicate) opt.flatMap(v -> anotherOptional) opt.or(() -> otherOptional)Create
of for definite values, ofNullable at boundaries (map lookups, old APIs), empty for “nothing”.
Transform
map, filter, flatMap work only when a value is present; empty passes through.
Unwrap
orElse, orElseGet for defaults; orElseThrow when the value is required.
Act
ifPresent and ifPresentOrElse run code without unwrapping.
Why not just call get()?
import java.util.*;
public class Main {
public static void main(String[] args) {
Optional<String> coupon = Optional.empty();
System.out.println("isPresent: " + coupon.isPresent());
try {
System.out.println(coupon.get());
} catch (NoSuchElementException e) {
System.out.println("get() on empty: " + e.getMessage());
}
System.out.println("safe: " + coupon.orElse("no coupon"));
}
}Use Optional for return values, not everywhere. Don't make fields, method parameters or collection elements Optional (List<Optional<X>>): it's not Serializable, adds noise, and an empty list is already a fine “nothing”. And never return null from a method whose type is Optional.
What happens in find(id).map(f).orElse(d)
The finder returns a box
Inside:Optional<String> name = findName("98100");Optional.ofNullable(map.get(phone)): a box holding the name, or the shared empty box.map checks the box
If a value is present,mapcalls your function and wraps the result (via ofNullable) in a new Optional. If empty, it returns empty without calling it.filter can empty it
If you add.filter(test), a present value that fails the test becomes empty.orElse unwraps
Present: return the value. Empty: return the fallback. Either way you get a plain value, never null (unless the fallback is null).Or fail clearly
When there's no sensible default, throwing a meaningful exception at this point is much easier to debug than an NPE three methods later..orElseThrow(() -> new IllegalArgumentException("unknown id " + id))
null vs Optional: both views
Left: what the caller sees with null and with Optional. Right: the Optional objects on the heap and what each method does with a full or empty box.
findByPhonereturns a Customer, ornullif the number isn't registered. The signature doesn't tell callers that.- One caller forgets the null check and calls a method on it:
NullPointerExceptionin production. - With
Optional<Customer>the return type itself says “maybe”. The value sits inside a box. map(Customer::name)transforms the value if present;orElse("Guest")unwraps it.- For an unknown number the method returns
Optional.empty(): a box with nothing inside, but never null. mapdoes nothing on an empty box, andorElsegives "Guest". Same code path, no if-null, no crash.
Optional.of(c)creates a small object (#1) whosevaluefield refers to the Customer. It's an extra object, so don't wrap things in hot loops for no reason.Optional.empty()returns a single shared instance whose value is null. All empty Optionals of any type are that same object.mapon a full box calls your function and wraps the result in a new Optional.mapon the empty box doesn't call your function at all; it returns empty.orElsereturns the value if present, otherwise the fallback.get()on an empty Optional throwsNoSuchElementException: No value present. That's just a different crash, so preferorElse,orElseGet,orElseThroworifPresent.
Optional in real systems
Data-access layers, configuration lookups and stream results are the most common places you'll meet Optional.
Watch it with an example: applying a coupon that may not exist
At checkout the app looks up the coupon code the user typed. It might be unknown, or expired. An Optional pipeline handles all cases in one chain.
find(code)returnsOptional<Coupon>;filterkeeps it only if valid;mapturns it into the discount;orElse(0)gives 0 otherwise.- SAVE50 exists and is valid: the box flows through filter and map, and the user gets ₹50 off.
- OLD20 exists but has expired:
filterreturns an empty Optional, so map is skipped and orElse gives 0. - HELLO doesn't exist: find returns empty, and every step passes the empty box along to orElse.
- The same four calls handle all three situations. The app can show “Coupon not valid” when the result is 0. (Illustrative codes.)
1. Database lookups
Repository methods like findById(id) in Spring Data return Optional<Customer>: the record may not exist.
2. Configuration
Optional settings get defaults; required ones fail at startup with a clear message.
3. Stream results
findFirst, min, max and reduce return Optional because a stream can be empty.
4. Optional user input
Coupons, referral codes and nicknames may or may not be supplied; Optional chains turn them into discounts or display names.
Simplified: APIs that return many results return an empty list, not an Optional list.
Greeting customers by phone number
import java.util.*;
public class Main {
static Map<String, String> customers = Map.of("98100", "Asha", "98200", "Ravi");
static Optional<String> findName(String phone) {
return Optional.ofNullable(customers.get(phone));
}
public static void main(String[] args) {
for (String phone : List.of("98100", "99999", "98200")) {
String greeting = findName(phone)
.map(name -> "Welcome back, " + name)
.orElse("Welcome! Please sign up");
System.out.println(phone + " -> " + greeting);
}
findName("98200").ifPresent(n -> System.out.println("Sending offer to " + n));
findName("99999").ifPresentOrElse(
n -> System.out.println("Sending offer to " + n),
() -> System.out.println("No customer for 99999"));
}
}Optional.ofNullable(customers.get(phone))Map lookups return null when the key is missing; wrap at the boundary..map(name -> "Welcome back, " + name)Runs only when the customer exists..orElse("Welcome! Please sign up")The answer for unknown numbers.ifPresentOrElse(..., ...)One action for present, another for empty.
A full box and an empty box
Recorded from the real JVM. Watch two Optional objects appear: one holding “Asha”, and the shared empty one. On the empty box, map's lambda never runs.
Worked examples: real problems, solved and traced in memory
Three common Optional patterns: a lookup with a default, a chain of optional steps, and failing loudly with orElseThrow when a value is required.
Plan name for a mobile number
Problem statement
A telecom app shows the customer's plan for a mobile number. Not every number is registered. Return Optional<String> from the lookup and display “No active plan” when it's empty.
numbers 98100 (registered), 97000 (not)
98100: Unlimited 599 97000: No active plan
Approach
planForwraps the map lookup withOptional.ofNullable.- The caller uses
orElsefor the default text. - No null checks in the caller.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Delivery city through optional steps
Problem statement
An order may have a customer, and a customer may have a saved address. Get the delivery city in upper case, or "PICKUP" if any step is missing, without a single null check.
order 1 (customer with address in Pune), order 2 (customer without address)
PUNE, PICKUP
Approach
- Model optional parts as
Optionalgetters. - Chain with
flatMap(the step itself returns an Optional) andmap(plain value). - End with
orElse("PICKUP").
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Fail fast when a value is required
Problem statement
A refund needs the original payment. If the payment id isn't found, the refund must stop with a clear error, not continue with missing data. Use orElseThrow.
refund for PAY-1 (exists), PAY-9 (missing)
refunded Rs 640 for PAY-1 refund failed: unknown payment PAY-9
Approach
findPaymentreturnsOptional<Integer>(the amount).orElseThrow(() -> new IllegalArgumentException(...))unwraps or throws.- The caller catches the exception and reports it.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 14.4 in one screen
- Optional = a box that holds one non-null value or nothing; use it as a return type.
- Create:
of(non-null),ofNullable,empty. - Unwrap:
orElse,orElseGet(lazy),orElseThrow. - Act:
ifPresent,ifPresentOrElse. Avoid bareget(). - Transform:
map,filter,flatMap; empty passes through untouched. - Streams'
findFirst,min,maxreturn Optional. - Not for fields, parameters or collections; never return null as an Optional.
Interview and viva questions
What problem does Optional solve?
It makes the possible absence of a result explicit in a method's return type, so callers are forced to handle the empty case, reducing NullPointerExceptions. It also provides a fluent API (map, filter, orElse) for working with possibly-missing values.
Difference between orElse and orElseGet?
orElse(x) takes an already-evaluated value, so the expression is computed even when the Optional has a value. orElseGet(supplier) calls the supplier only when the Optional is empty. Use orElseGet when the default is expensive or has side effects.
Difference between map and flatMap on Optional?
map applies a function returning a plain value and wraps it; flatMap applies a function that already returns an Optional, avoiding Optional<Optional<T>>. Use flatMap to chain lookups that each may be empty.
Should you use Optional as a field or parameter?
Generally no. Optional isn't Serializable, adds allocation, and as a parameter it forces callers to wrap values. Prefer overloading or nullable parameters documented clearly for inputs, and plain nullable fields with Optional-returning getters.
Is Optional.get() bad?
It throws NoSuchElementException on empty, so isPresent() + get() is just a disguised null check. Prefer orElse/orElseThrow/ifPresent/map. Java 10 added orElseThrow() as a clearer name for the same behaviour as get().
Practice problems
Create Optionals, unwrap them safely, and chain map, filter and flatMap. Every output is fixed. The last three are business tasks.
of, empty, ofNullable
Create an Optional with a value, an empty one, and one from a null reference, and print all three.
(none)
Optional[chai] Optional.empty Optional.empty
Show solution and explanation
import java.util.Optional;
public class Main {
public static void main(String[] args) {
String missing = null;
System.out.println(Optional.of("chai") + " " + Optional.empty() + " " + Optional.ofNullable(missing));
}
}How it works: of requires a non-null value (it throws NPE on null); ofNullable accepts null and returns empty.
orElse
Print the nickname of a user if present, else their full name.
nickname missing
Hello, Meenakshi Iyer
Show solution and explanation
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> nickname = Optional.empty();
String fullName = "Meenakshi Iyer";
System.out.println("Hello, " + nickname.orElse(fullName));
}
}How it works: orElse returns the value or the given fallback.
ifPresent
Print an SMS only if a phone number is present. Try with one present and one empty.
98100 / none
SMS sent to 98100 done
Show solution and explanation
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> p1 = Optional.of("98100");
Optional<String> p2 = Optional.empty();
p1.ifPresent(n -> System.out.println("SMS sent to " + n));
p2.ifPresent(n -> System.out.println("SMS sent to " + n));
System.out.println("done");
}
}How it works: the Consumer runs only when a value is present; nothing happens for empty.
orElse vs orElseGet
Show that orElse evaluates its argument even when a value is present, while orElseGet doesn't.
(none)
orElse: (loading default from server...) dark orElseGet: dark
Show solution and explanation
import java.util.Optional;
public class Main {
static String loadDefault() {
System.out.println(" (loading default from server...)");
return "default";
}
public static void main(String[] args) {
Optional<String> theme = Optional.of("dark");
System.out.println("orElse:");
System.out.println(theme.orElse(loadDefault()));
System.out.println("orElseGet:");
System.out.println(theme.orElseGet(Main::loadDefault));
}
}How it works: orElse(x) receives an already-computed value, so loadDefault() runs first anyway. orElseGet takes a Supplier and calls it only when empty: use it when the fallback is expensive.
map and filter on Optional
From an optional email, keep it only if it ends with "@company.in", and return the part before @, else "external".
asha@company.in / ravi@gmail.com
asha, external
Show solution and explanation
import java.util.Optional;
public class Main {
static String userId(Optional<String> email) {
return email.filter(e -> e.endsWith("@company.in"))
.map(e -> e.substring(0, e.indexOf('@')))
.orElse("external");
}
public static void main(String[] args) {
System.out.println(userId(Optional.of("asha@company.in")) + ", " + userId(Optional.of("ravi@gmail.com")));
}
}How it works: filter turns a full box empty if the test fails; map transforms only full boxes.
Optional from a stream
Find the first order above ₹5,000; print it or "none". Then find the cheapest order.
450, 1200, 2600
big: none, cheapest: 450
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> orders = List.of(450, 1200, 2600);
Optional<Integer> big = orders.stream().filter(o -> o > 5000).findFirst();
Optional<Integer> cheapest = orders.stream().min(Integer::compare);
System.out.println("big: " + big.map(String::valueOf).orElse("none") + ", cheapest: " + cheapest.orElse(0));
}
}How it works: stream methods that might have no answer (findFirst, min, max, reduce without identity) return Optional.
Loyalty points or welcome bonus
Look up loyalty points by customer id; new customers get a 50-point welcome bonus. Use Optional.ofNullable on a map lookup.
ids C1 (320 points), C9 (new)
C1: 320, C9: 50
Show solution and explanation
import java.util.*;
public class Main {
static Map<String, Integer> points = Map.of("C1", 320, "C2", 75);
static int pointsFor(String id) {
return Optional.ofNullable(points.get(id)).orElse(50);
}
public static void main(String[] args) {
System.out.println("C1: " + pointsFor("C1") + ", C9: " + pointsFor("C9"));
}
}How it works: wrapping a possibly-null lookup at the boundary keeps nulls from spreading. (For maps alone, getOrDefault does the same job.)
Shipping charge rule chain
An order may have a pincode. If present and it starts with "4" (Maharashtra), shipping is ₹0; if present but elsewhere, ₹49; if missing, ask for it.
pincodes 411001, 560001, none
free shipping Rs 49 shipping please enter a pincode
Show solution and explanation
import java.util.*;
public class Main {
static String shipping(Optional<String> pin) {
return pin.map(p -> p.startsWith("4") ? "free shipping" : "Rs 49 shipping")
.orElse("please enter a pincode");
}
public static void main(String[] args) {
System.out.println(shipping(Optional.of("411001")));
System.out.println(shipping(Optional.of("560001")));
System.out.println(shipping(Optional.empty()));
}
}How it works: map handles the “present” logic, orElse the “missing” case. (Illustrative rule.)
Required config value
An app reads the GST rate from config. If it's missing, startup must fail with a clear message. Show both cases.
config with and without gst.rate
rate: 0.18 startup stopped: gst.rate missing in config
Show solution and explanation
import java.util.*;
public class Main {
static double gstRate(Map<String, String> config) {
return Optional.ofNullable(config.get("gst.rate"))
.map(Double::parseDouble)
.orElseThrow(() -> new IllegalStateException("gst.rate missing in config"));
}
public static void main(String[] args) {
System.out.println("rate: " + gstRate(Map.of("gst.rate", "0.18")));
try {
gstRate(Map.of());
} catch (IllegalStateException e) {
System.out.println("startup stopped: " + e.getMessage());
}
}
}How it works: failing at startup with a precise message is far better than running with a wrong tax rate. orElseThrow makes “required” explicit.
Real-world scenario questions (asked in company interviews)
Optional questions test whether you can remove NPEs without making the code worse.
NullPointerException on the order-tracking page
The tracking page calls order.getRider().getPhone(). Orders that aren't assigned to a rider yet crash the page. How would you fix it so the page shows “Rider not assigned yet”?
The interviewer is checking: chaining optional relationships.
Show a strong answer
- Make the getter honest:
Optional<Rider> rider(). - Page code:
order.rider().map(Rider::phone).orElse("Rider not assigned yet"). - Search for other callers of
getRider(); the new type makes the compiler point them out.
Avoid: wrapping the whole page in try { … } catch (NullPointerException e).
Every request hits the pricing service
Code reads cachedPrice(id).orElse(pricingService.fetch(id)). Monitoring shows the pricing service is called on every request, even when the cache has the price. Why?
The interviewer is checking: orElse vs orElseGet.
Show a strong answer
orElse's argument is evaluated before the call, sofetch(id)always runs.- Use
orElseGet(() -> pricingService.fetch(id)): the supplier runs only on a cache miss.
Avoid: adding an if-isPresent around it; orElseGet already expresses it.
Optional everywhere
A pull request changes every entity field to Optional<...> and every method parameter too, “to remove nulls from the codebase”. Serialization of sessions now fails. What feedback would you give?
The interviewer is checking: knowing Optional's intended scope.
Show a strong answer
- Optional is designed for return types. It's not Serializable, so fields break serialization and many frameworks.
- Keep fields as plain (possibly null) references and expose
Optional-returning getters for the optional ones. - For parameters, use overloads or validate non-null inputs with
Objects.requireNonNull.
Avoid: approving because it “looks modern”.
Empty Optional or exception?
A PaymentService.getPayment(id) is called from refunds (where the id must exist) and from a search box (where it may not). Should it return Optional<Payment> or throw when not found?
The interviewer is checking: designing for callers.
Show a strong answer
- Return
Optional<Payment>from the lookup: “not found” is a normal outcome for search. - Refunds call
getPayment(id).orElseThrow(() -> new PaymentNotFound(id)), choosing their own exception and message. - One method serves both cases; each caller decides what absence means.
Avoid: returning null for search and throwing for refunds from two near-identical methods.