Java · lesson 30 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Optional in Java
Learn Optional<T> by running it: create, unwrap with orElse vs orElseGet, transform with map, flatMap and filter, use it in streams, and avoid misuse.
By SkillByExample · Updated
Optional<T> is a container that holds either one value or nothing. Returning it instead of null
puts “there might be no result” into the method’s signature, where the caller cannot miss it.
Every example is a single file run with the JDK 21 source launcher (java File.java).
The problem: null is invisible
import java.util.Map;
public class NullProblem {
static final Map<Integer, String> EMAILS = Map.of(1, "ada@example.com");
static String findEmail(int userId) {
return EMAILS.get(userId); // null when missing
}
public static void main(String[] args) {
System.out.println(findEmail(1).toUpperCase());
System.out.println(findEmail(2).toUpperCase());
}
}
$ java NullProblem.java
ADA@EXAMPLE.COM
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because the return value of "NullProblem.findEmail(int)" is null
at NullProblem.main(NullProblem.java:12)
Nothing in String findEmail(int) warns that it can return null, so the caller dereferenced it.
The fix is not more discipline but a return type that says so.
Creating Optionals
import java.util.Optional;
public class Creating {
public static void main(String[] args) {
Optional<String> some = Optional.of("value");
Optional<String> none = Optional.empty();
String maybe = null;
Optional<String> fromNullable = Optional.ofNullable(maybe);
System.out.println(some + " " + none + " " + fromNullable);
System.out.println(some.isPresent() + " " + none.isEmpty());
Optional.of(maybe);
}
}
$ java Creating.java
Optional[value] Optional.empty Optional.empty
true true
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at java.base/java.util.Optional.of(Optional.java:113)
at Creating.main(Creating.java:12)
Optional.of is for values you know are non-null and fails fast otherwise; ofNullable converts a
possibly-null value; empty() is the explicit “no result”.
Unwrapping: orElse, orElseGet, orElseThrow
import java.util.Optional;
public class Unwrap {
static String expensiveDefault() {
System.out.println(" (computing default)");
return "guest";
}
public static void main(String[] args) {
Optional<String> name = Optional.of("ada");
Optional<String> none = Optional.empty();
System.out.println("orElse on present:");
System.out.println(name.orElse(expensiveDefault()));
System.out.println("orElseGet on present:");
System.out.println(name.orElseGet(Unwrap::expensiveDefault));
System.out.println("orElseGet on empty:");
System.out.println(none.orElseGet(Unwrap::expensiveDefault));
name.ifPresentOrElse(n -> System.out.println("hello " + n), () -> System.out.println("nobody"));
none.orElseThrow(() -> new IllegalStateException("user required"));
}
}
$ java Unwrap.java
orElse on present:
(computing default)
ada
orElseGet on present:
ada
orElseGet on empty:
(computing default)
guest
hello ada
Exception in thread "main" java.lang.IllegalStateException: user required
at Unwrap.lambda$main$2(Unwrap.java:21)
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
at Unwrap.main(Unwrap.java:21)
orElse’s argument is an ordinary expression, evaluated before the call — so the default was
computed even though name had a value. orElseGet takes a supplier and only calls it when needed.
orElseThrow turns absence into an exception of your choosing; without arguments it throws
NoSuchElementException, which is what bare get() does too.
Transforming with map
map applies a function to the value if there is one, and passes emptiness through untouched.
import java.util.Map;
import java.util.Optional;
public class Mapping {
static final Map<Integer, String> EMAILS = Map.of(1, "ada@example.com");
static Optional<String> findEmail(int id) {
return Optional.ofNullable(EMAILS.get(id));
}
public static void main(String[] args) {
for (int id : new int[] {1, 2}) {
String domain = findEmail(id)
.map(e -> e.substring(e.indexOf('@') + 1))
.map(String::toUpperCase)
.orElse("(no email)");
System.out.println(id + " -> " + domain);
}
}
}
$ java Mapping.java
1 -> EXAMPLE.COM
2 -> (no email)
The chain reads as the happy path, with no if (x != null) between steps. If any step’s function
returns null, map turns it into an empty Optional too.
flatMap and filter
When the function itself returns an Optional, map would nest them; flatMap flattens.
import java.util.Map;
import java.util.Optional;
public class FlatMapDemo {
record User(String name, Optional<String> managerName) {}
static final Map<String, User> USERS = Map.of(
"ada", new User("ada", Optional.of("grace")),
"grace", new User("grace", Optional.empty()));
static Optional<User> find(String name) { return Optional.ofNullable(USERS.get(name)); }
public static void main(String[] args) {
Optional<Optional<String>> nested = find("ada").map(User::managerName);
System.out.println("map: " + nested);
System.out.println("flatMap: " + find("ada").flatMap(User::managerName));
System.out.println("grace's: " + find("grace").flatMap(User::managerName));
System.out.println("nobody: " + find("bob").flatMap(User::managerName));
System.out.println(find("ada").filter(u -> u.name().length() > 3).isPresent());
}
}
$ java FlatMapDemo.java
map: Optional[Optional[grace]]
flatMap: Optional[grace]
grace's: Optional.empty
nobody: Optional.empty
false
filter keeps the value only if the predicate holds. The record field Optional<String> managerName is used here only to keep the example short — the next section explains why fields
should usually not be Optional.
Optional in streams
Terminal operations that may find nothing return Optional, and Optional.stream() lets a stream
of optionals drop the empty ones.
import java.util.List;
import java.util.Optional;
public class StreamOptional {
static Optional<Integer> parse(String s) {
try {
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public static void main(String[] args) {
List<String> raw = List.of("10", "x", "25", "", "7");
List<Integer> nums = raw.stream().map(StreamOptional::parse).flatMap(Optional::stream).toList();
System.out.println(nums);
System.out.println(nums.stream().max(Integer::compare));
System.out.println(nums.stream().filter(n -> n > 100).findFirst());
}
}
$ java StreamOptional.java
[10, 25, 7]
Optional[25]
Optional.empty
flatMap(Optional::stream) maps each Optional to zero or one elements, so the unparseable
strings simply disappear. max and findFirst return Optional because an empty stream has no
answer.
What not to do
import java.util.Optional;
public class Misuse {
static Optional<String> lookup(boolean found) {
return found ? Optional.of("value") : Optional.empty();
}
public static void main(String[] args) {
Optional<String> result = lookup(true);
if (result.isPresent()) { // works, but is just a null check
System.out.println(result.get());
}
System.out.println(lookup(true).orElse("none")); // say what you mean
Optional<String> opt = lookup(false);
System.out.println(opt.get());
}
}
$ java Misuse.java
value
value
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at Misuse.main(Misuse.java:16)
isPresent() followed by get() is the null check in disguise, and an unguarded get() fails just
like a null dereference. Prefer orElse, orElseGet, map or ifPresent. Also avoid Optional
fields, parameters and collections of Optional — return an empty list instead of
Optional<List<T>>.
Practice
- Rewrite
NullProblem.findEmailto returnOptional<String>and print each user’s email in lower case, or"no email on file".
Solution
import java.util.Map;
import java.util.Optional;
public class FixedEmail {
static final Map<Integer, String> EMAILS = Map.of(1, "Ada@Example.com");
static Optional<String> findEmail(int userId) {
return Optional.ofNullable(EMAILS.get(userId));
}
public static void main(String[] args) {
for (int id = 1; id <= 2; id++) {
System.out.println(id + ": " + findEmail(id).map(String::toLowerCase).orElse("no email on file"));
}
}
}
$ java FixedEmail.java
1: ada@example.com
2: no email on file
The signature now forces the caller to choose a fallback; forgetting is no longer possible without
calling get().
- Read a port from a config map: if the key is missing, or the value is not a number between 1 and
65535, fall back to
8080.
Solution
import java.util.Map;
import java.util.Optional;
public class Port {
static int port(Map<String, String> config) {
return Optional.ofNullable(config.get("port"))
.filter(s -> s.matches("\\d+"))
.map(Integer::parseInt)
.filter(p -> p >= 1 && p <= 65535)
.orElse(8080);
}
public static void main(String[] args) {
System.out.println(port(Map.of("port", "9000")));
System.out.println(port(Map.of("port", "99999")));
System.out.println(port(Map.of("port", "abc")));
System.out.println(port(Map.of()));
}
}
$ java Port.java
9000
8080
8080
8080
Each filter or map that fails turns the chain empty, and one orElse at the end handles every
failure case.
- Show that
orElseruns its argument even when the value is present by counting calls to a default-making method, then switch toorElseGetand count again.
Solution
import java.util.Optional;
public class CountDefaults {
static int calls = 0;
static String makeDefault() {
calls++;
return "default";
}
public static void main(String[] args) {
Optional<String> present = Optional.of("real");
for (int i = 0; i < 3; i++) present.orElse(makeDefault());
System.out.println("orElse calls: " + calls);
calls = 0;
for (int i = 0; i < 3; i++) present.orElseGet(CountDefaults::makeDefault);
System.out.println("orElseGet calls: " + calls);
}
}
$ java CountDefaults.java
orElse calls: 3
orElseGet calls: 0
Java evaluates method arguments before the call, and orElse has no way to stop that.
- Given a list of names, find the first one starting with
"Z"and print it, or print"no Z names"usingifPresentOrElse.
Solution
import java.util.List;
public class FirstZ {
public static void main(String[] args) {
for (List<String> names : List.of(List.of("Ann", "Zoe", "Zed"), List.of("Ann", "Bob"))) {
names.stream()
.filter(n -> n.startsWith("Z"))
.findFirst()
.ifPresentOrElse(System.out::println, () -> System.out.println("no Z names"));
}
}
}
$ java FirstZ.java
Zoe
no Z names
findFirst returns Optional<String>, and ifPresentOrElse handles both outcomes without an if.