- Optional Basics (Foundation)
- Creating Optional Instances
- Checking for Values
- Retrieving Values
- Transforming Values (map & flatMap)
- Filtering Values
- Default Values & Alternatives
- Conditional Actions
- Optional with Streams
- Combining Optionals
- Optional Best Practices ⭐
- Common Anti-Patterns to AVOID ❌
- Real-World Use Cases
- Interview Problems
- Advanced Patterns
| Method | Use Case | Example |
|---|---|---|
Optional.ofNullable() |
Most common - when value might be null | Optional.ofNullable(getUserName()) |
map() |
Transform value if present | optional.map(String::toUpperCase) |
flatMap() |
Transform to another Optional | optional.flatMap(this::findUser) |
filter() |
Conditionally keep value | optional.filter(s -> s.length() > 5) |
orElse() |
Simple default value | optional.orElse("default") |
orElseGet() |
Computed default value | optional.orElseGet(() -> computeDefault()) |
orElseThrow() |
Fail if absent | optional.orElseThrow(() -> new Exception()) |
ifPresent() |
Execute action if present | optional.ifPresent(System.out::println) |
ifPresentOrElse() |
Action for both cases (Java 9+) | optional.ifPresentOrElse(use, handleEmpty) |
stream() |
Convert to Stream (Java 9+) | optional.stream().collect(toList()) |
or() |
Alternative Optional (Java 9+) | optional.or(() -> findAlternative()) |
| Method | Issue | Better Alternative |
|---|---|---|
Optional.of() |
Throws NPE if null | Use Optional.ofNullable() |
get() |
Throws NoSuchElementException | Use orElse(), orElseGet(), or orElseThrow() |
isPresent() |
Often leads to imperative code | Use ifPresent(), map(), or orElse() |
- Don't use Optional for fields
- Don't use Optional for method parameters
- Don't use Optional for collections (return empty collection instead)
- Don't call
get()without checkingisPresent() - Don't use
isPresent() + get()combo (use functional methods)
Optional is a container object that may or may not contain a non-null value. It's designed to represent "optional" return values and avoid NullPointerException.
Key Characteristics:
- Wrapper around potentially null value
- Forces explicit handling of absence
- Encourages functional programming style
- Reduces null checks and NPE risks
Before Optional (Bad):
public String getUserName(User user) {
if (user != null) {
if (user.getName() != null) {
return user.getName().toUpperCase();
}
}
return "UNKNOWN";
}With Optional (Good):
public String getUserName(Optional<User> user) {
return user.map(User::getName)
.map(String::toUpperCase)
.orElse("UNKNOWN");
}| Null | Optional |
|---|---|
| Implicit absence | Explicit absence |
| NullPointerException risk | Compile-time safety |
| No API for handling | Rich functional API |
| Can be forgotten | Forces handling |
Problem 1: Basic Optional Understanding
public void demonstrateOptional() {
// Empty Optional
Optional<String> empty = Optional.empty();
System.out.println("Empty: " + empty.isPresent()); // false
// Optional with value
Optional<String> present = Optional.of("Hello");
System.out.println("Present: " + present.isPresent()); // true
// Optional from nullable
Optional<String> nullable = Optional.ofNullable(null);
System.out.println("Nullable: " + nullable.isPresent()); // false
}Problem 2: Create Empty Optional
public Optional<String> getEmptyOptional() {
return Optional.empty();
}
public Optional<User> findUserById(Long id) {
// User not found
return Optional.empty();
}Problem 3: Create Optional with Non-Null Value
public Optional<String> createWithOf() {
String value = "Hello";
return Optional.of(value); // OK - value is not null
}
public Optional<String> dangerousOf() {
String value = null;
// return Optional.of(value); // ❌ Throws NullPointerException!
return Optional.ofNullable(value); // ✅ Correct way
}Problem 4: Create Optional from Nullable Value
public Optional<String> safeCreation(String value) {
return Optional.ofNullable(value); // ✅ Safe for null
}
public Optional<User> findUser(String username) {
User user = database.findByUsername(username); // might return null
return Optional.ofNullable(user);
}
public Optional<String> getProperty(String key) {
return Optional.ofNullable(System.getProperty(key));
}Problem 5: Check if Value Exists
// ❌ BAD: Imperative style
public void badIsPresent(Optional<String> optional) {
if (optional.isPresent()) {
System.out.println(optional.get());
}
}
// ✅ GOOD: Functional style
public void goodIsPresent(Optional<String> optional) {
optional.ifPresent(System.out::println);
}Problem 6: Check if Empty
public boolean isEmptyCheck(Optional<String> optional) {
return optional.isEmpty(); // Java 11+
}
// Before Java 11
public boolean isEmptyLegacy(Optional<String> optional) {
return !optional.isPresent();
}Problem 7: Retrieve Value (Dangerous)
// ❌ DANGEROUS: Throws NoSuchElementException if empty
public String dangerousGet(Optional<String> optional) {
return optional.get(); // Can throw exception!
}
// ✅ SAFE: Check before get (but still not recommended)
public String safeButNotIdeal(Optional<String> optional) {
if (optional.isPresent()) {
return optional.get();
}
return "default";
}
// ✅ BEST: Use orElse or orElseGet
public String bestPractice(Optional<String> optional) {
return optional.orElse("default");
}Problem 8: Provide Default Value
public String getNameOrDefault(Optional<String> name) {
return name.orElse("Anonymous");
}
public int getAgeOrDefault(Optional<Integer> age) {
return age.orElse(0);
}
public User getUserOrDefault(Optional<User> user) {
return user.orElse(new User("Guest"));
}Problem 9: Lazy Default Value
// ❌ BAD: orElse always executes
public String badDefault(Optional<String> name) {
return name.orElse(computeExpensiveDefault()); // Always computed!
}
// ✅ GOOD: orElseGet only executes when needed
public String goodDefault(Optional<String> name) {
return name.orElseGet(() -> computeExpensiveDefault()); // Lazy!
}
private String computeExpensiveDefault() {
System.out.println("Computing expensive default...");
return "Default";
}
public User getUserOrCreate(Optional<User> user) {
return user.orElseGet(() -> {
System.out.println("Creating new user");
return new User("Default");
});
}Problem 10: Throw Exception if Empty
public String getOrThrow(Optional<String> name) {
return name.orElseThrow(); // Java 10+, throws NoSuchElementException
}
public User getUserOrThrow(Optional<User> user) {
return user.orElseThrow(() ->
new UserNotFoundException("User not found"));
}
public String getRequiredProperty(String key) {
return Optional.ofNullable(System.getProperty(key))
.orElseThrow(() ->
new IllegalStateException("Property " + key + " not set"));
}Problem 11: Transform Value
public Optional<Integer> getLength(Optional<String> text) {
return text.map(String::length);
}
public Optional<String> toUpperCase(Optional<String> text) {
return text.map(String::toUpperCase);
}
public Optional<Double> calculatePrice(Optional<Integer> quantity) {
return quantity.map(q -> q * 9.99);
}Problem 12: Chain Transformations
public String processUser(Optional<User> user) {
return user.map(User::getName)
.map(String::toUpperCase)
.map(name -> "Hello, " + name)
.orElse("Hello, Guest");
}
class User {
private String name;
private Address address;
public String getName() { return name; }
public Address getAddress() { return address; }
}
class Address {
private String city;
public String getCity() { return city; }
}Problem 13: Flatten Nested Optionals
// ❌ BAD: Returns Optional<Optional<String>>
public Optional<Optional<String>> badNesting(Optional<User> user) {
return user.map(u -> Optional.ofNullable(u.getName()));
}
// ✅ GOOD: Returns Optional<String>
public Optional<String> goodFlattening(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getName()));
}
public Optional<String> getCityName(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getAddress()))
.flatMap(a -> Optional.ofNullable(a.getCity()));
}Problem 14: Complex Chaining
public Optional<String> getUpperCityName(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getAddress()))
.flatMap(a -> Optional.ofNullable(a.getCity()))
.map(String::toUpperCase);
}
class UserRepository {
public Optional<User> findById(Long id) {
// Database lookup
return Optional.ofNullable(database.get(id));
}
}
public Optional<String> getUserEmail(Long userId) {
return repository.findById(userId)
.flatMap(user -> Optional.ofNullable(user.getEmail()));
}Problem 15: Conditional Filtering
public Optional<String> filterLongStrings(Optional<String> text) {
return text.filter(s -> s.length() > 5);
}
public Optional<Integer> filterPositive(Optional<Integer> number) {
return number.filter(n -> n > 0);
}
public Optional<User> filterAdult(Optional<User> user) {
return user.filter(u -> u.getAge() >= 18);
}Problem 16: Chaining Filters
public Optional<String> complexFilter(Optional<String> text) {
return text.filter(s -> s.length() > 3)
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase);
}
public Optional<User> filterValidUser(Optional<User> user) {
return user.filter(u -> u.getAge() >= 18)
.filter(u -> u.getEmail() != null)
.filter(u -> !u.getEmail().isEmpty());
}Problem 17: Performance Comparison
public void demonstrateOrElseDifference() {
Optional<String> value = Optional.of("Present");
// orElse ALWAYS evaluates the default
String result1 = value.orElse(computeExpensive()); // ❌ Always computed!
// orElseGet ONLY evaluates when needed
String result2 = value.orElseGet(() -> computeExpensive()); // ✅ Not computed!
}
private String computeExpensive() {
System.out.println("Computing...");
return "Expensive";
}
// Rule of thumb:
// - Use orElse() for simple constants
// - Use orElseGet() for method calls or object creationProblem 18: Alternative Optional
public Optional<String> getFromCacheOrDatabase(String key) {
return cache.get(key)
.or(() -> database.get(key))
.or(() -> Optional.of("default"));
}
public Optional<User> findUser(String username) {
return primaryDb.findByUsername(username)
.or(() -> secondaryDb.findByUsername(username))
.or(() -> createGuestUser(username));
}
private Optional<User> createGuestUser(String username) {
return Optional.of(new User(username, "guest"));
}Problem 19: Execute Action if Present
public void printIfPresent(Optional<String> name) {
name.ifPresent(System.out::println);
}
public void saveUser(Optional<User> user) {
user.ifPresent(u -> database.save(u));
}
public void logIfPresent(Optional<String> message) {
message.ifPresent(msg -> logger.info("Message: {}", msg));
}Problem 20: Handle Both Present and Absent
public void processUser(Optional<User> user) {
user.ifPresentOrElse(
u -> System.out.println("Found: " + u.getName()),
() -> System.out.println("User not found")
);
}
public void handleOptional(Optional<String> value) {
value.ifPresentOrElse(
val -> processValue(val),
() -> handleMissing()
);
}
private void processValue(String val) {
System.out.println("Processing: " + val);
}
private void handleMissing() {
System.out.println("No value to process");
}Problem 21: Convert Optional to Stream
public List<String> optionalToList(Optional<String> optional) {
return optional.stream()
.collect(Collectors.toList());
}
public List<String> filterOptionals(List<Optional<String>> optionals) {
return optionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}Problem 22: Process Multiple Optionals
public List<User> getAllPresentUsers(List<Optional<User>> optionalUsers) {
return optionalUsers.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
public List<String> getNonEmptyNames(List<Optional<String>> names) {
return names.stream()
.flatMap(Optional::stream)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
}Problem 23: Combine with map and flatMap
public Optional<String> combineNames(Optional<String> firstName,
Optional<String> lastName) {
return firstName.flatMap(first ->
lastName.map(last -> first + " " + last)
);
}
public Optional<Integer> addOptionals(Optional<Integer> a,
Optional<Integer> b) {
return a.flatMap(valA ->
b.map(valB -> valA + valB)
);
}Problem 24: Multiple Optional Validation
public Optional<User> createUser(Optional<String> name,
Optional<String> email,
Optional<Integer> age) {
return name.flatMap(n ->
email.flatMap(e ->
age.map(a -> new User(n, e, a))
)
);
}
class User {
String name;
String email;
Integer age;
public User(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
}Problem 25: Optional Return Types
// ✅ GOOD: Clear that value might be absent
public Optional<User> findUserById(Long id) {
User user = database.get(id);
return Optional.ofNullable(user);
}
// ✅ GOOD: Repository methods
public Optional<String> getConfigValue(String key) {
return Optional.ofNullable(configMap.get(key));
}Problem 26: Optional as Fields (Anti-pattern)
// ❌ BAD: Optional as field
class BadUser {
private Optional<String> name; // Wrong!
private Optional<Integer> age; // Wrong!
}
// ✅ GOOD: Nullable fields
class GoodUser {
private String name; // Can be null
private Integer age; // Can be null
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<Integer> getAge() {
return Optional.ofNullable(age);
}
}Problem 27: Optional Parameters (Anti-pattern)
// ❌ BAD: Optional parameter
public void processUser(Optional<User> user) {
user.ifPresent(this::save);
}
// ✅ GOOD: Overloaded methods or null check
public void processUser(User user) {
if (user != null) {
save(user);
}
}
// ✅ BETTER: Separate methods
public void processUser(User user) {
save(user);
}
public void processNoUser() {
// Handle no user case
}Problem 28: Collections (Anti-pattern)
// ❌ BAD: Optional collection
public Optional<List<User>> getUsers() {
return Optional.ofNullable(userList);
}
// ✅ GOOD: Return empty collection
public List<User> getUsers() {
return userList != null ? userList : Collections.emptyList();
}
// ✅ GOOD: Stream or empty
public List<String> getNames() {
return names != null ? names : List.of();
}Problem 29: Functional Chaining
// ❌ BAD: Imperative style
public String getUserCityBad(Optional<User> user) {
if (user.isPresent()) {
User u = user.get();
if (u.getAddress() != null) {
Address addr = u.getAddress();
if (addr.getCity() != null) {
return addr.getCity().toUpperCase();
}
}
}
return "UNKNOWN";
}
// ✅ GOOD: Functional style
public String getUserCityGood(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getAddress()))
.flatMap(a -> Optional.ofNullable(a.getCity()))
.map(String::toUpperCase)
.orElse("UNKNOWN");
}Problem 30: Avoid isPresent + get
// ❌ BAD: Defeats the purpose of Optional
public String antiPattern1(Optional<String> optional) {
if (optional.isPresent()) {
return optional.get();
}
return "default";
}
// ✅ GOOD: Use orElse
public String bestPractice1(Optional<String> optional) {
return optional.orElse("default");
}Problem 31: Avoid Optional.of with Null
// ❌ BAD: Throws NullPointerException
public Optional<String> antiPattern2(String value) {
return Optional.of(value); // Dangerous if value is null!
}
// ✅ GOOD: Use ofNullable
public Optional<String> bestPractice2(String value) {
return Optional.ofNullable(value);
}Problem 32: Unchecked get()
// ❌ BAD: Can throw NoSuchElementException
public String antiPattern3(Optional<String> optional) {
return optional.get();
}
// ✅ GOOD: Use safe extraction
public String bestPractice3(Optional<String> optional) {
return optional.orElse("default");
}Problem 33: Null Returns
// ❌ BAD: Returns null
public Optional<User> antiPattern4(Long id) {
User user = database.get(id);
return user != null ? Optional.of(user) : null; // Wrong!
}
// ✅ GOOD: Return empty Optional
public Optional<User> bestPractice4(Long id) {
User user = database.get(id);
return Optional.ofNullable(user);
}Problem 34: Avoid Optional<Optional>
// ❌ BAD: Nested Optionals
public Optional<Optional<String>> antiPattern5(Optional<User> user) {
return user.map(u -> Optional.ofNullable(u.getName()));
}
// ✅ GOOD: Use flatMap
public Optional<String> bestPractice5(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getName()));
}Problem 35: Primitive Optionals
// ❌ BAD: Boxing overhead
public Optional<Integer> antiPattern6(int value) {
return Optional.of(value);
}
// ✅ GOOD: Use specialized OptionalInt
public OptionalInt bestPractice6(int value) {
return OptionalInt.of(value);
}
// Similarly use OptionalLong and OptionalDouble
public OptionalLong getLong(long value) {
return OptionalLong.of(value);
}
public OptionalDouble getDouble(double value) {
return OptionalDouble.of(value);
}Problem 36: Safe Configuration Access
public class ConfigManager {
private Map<String, String> config = new HashMap<>();
public Optional<String> getConfig(String key) {
return Optional.ofNullable(config.get(key));
}
public int getPort() {
return getConfig("server.port")
.map(Integer::parseInt)
.orElse(8080);
}
public String getHost() {
return getConfig("server.host")
.orElse("localhost");
}
public boolean isSecure() {
return getConfig("server.secure")
.map(Boolean::parseBoolean)
.orElse(false);
}
}Problem 37: Safe Profile Access
public class UserService {
private UserRepository repository;
public Optional<User> findUserByEmail(String email) {
return repository.findByEmail(email);
}
public String getUserDisplayName(String email) {
return findUserByEmail(email)
.map(User::getName)
.orElse("Anonymous");
}
public Optional<String> getUserCity(String email) {
return findUserByEmail(email)
.flatMap(user -> Optional.ofNullable(user.getAddress()))
.flatMap(addr -> Optional.ofNullable(addr.getCity()));
}
}Problem 38: Handle API Responses
public class ApiClient {
public Optional<JsonNode> getResponse(String url) {
try {
String response = httpClient.get(url);
return Optional.ofNullable(parseJson(response));
} catch (Exception e) {
return Optional.empty();
}
}
public String extractField(String url, String fieldName) {
return getResponse(url)
.flatMap(json -> Optional.ofNullable(json.get(fieldName)))
.map(JsonNode::asText)
.orElse("N/A");
}
private JsonNode parseJson(String response) {
// JSON parsing logic
return null;
}
}Problem 39: Safe Database Access
public class ProductRepository {
public Optional<Product> findById(Long id) {
Product product = database.query("SELECT * FROM products WHERE id = ?", id);
return Optional.ofNullable(product);
}
public Optional<Product> findByName(String name) {
return Optional.ofNullable(
database.query("SELECT * FROM products WHERE name = ?", name)
);
}
public double getProductPrice(Long id) {
return findById(id)
.map(Product::getPrice)
.orElse(0.0);
}
public boolean isInStock(Long id) {
return findById(id)
.map(Product::getStock)
.filter(stock -> stock > 0)
.isPresent();
}
}Problem 40: Sequential Validation
public class Validator {
public Optional<User> validateUser(User user) {
return Optional.of(user)
.filter(u -> u.getName() != null)
.filter(u -> u.getName().length() >= 3)
.filter(u -> u.getEmail() != null)
.filter(u -> u.getEmail().contains("@"))
.filter(u -> u.getAge() >= 18);
}
public String validateAndGetName(User user) {
return validateUser(user)
.map(User::getName)
.orElseThrow(() ->
new ValidationException("Invalid user"));
}
}public Optional<Integer> findFirstEven(List<Integer> numbers) {
return numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
}public String getUserEmail(Optional<User> user) {
return user.flatMap(u -> Optional.ofNullable(u.getEmail()))
.orElse("no-email@example.com");
}public Optional<String> getCompanyName(Long userId) {
return userRepository.findById(userId)
.flatMap(user -> Optional.ofNullable(user.getCompany()))
.flatMap(company -> Optional.ofNullable(company.getName()));
}
class Company {
private String name;
public String getName() { return name; }
}public Optional<String> getAdultUserName(Long userId) {
return userRepository.findById(userId)
.filter(user -> user.getAge() >= 18)
.map(User::getName)
.map(String::toUpperCase);
}public Optional<Double> calculateTotal(Optional<Double> price,
Optional<Integer> quantity) {
return price.flatMap(p ->
quantity.map(q -> p * q)
);
}public Optional<Integer> parseInteger(String value) {
try {
return Optional.of(Integer.parseInt(value));
} catch (NumberFormatException e) {
return Optional.empty();
}
}public User getOrCreateUser(Optional<User> user) {
return user.orElseGet(() -> {
System.out.println("Creating new user");
return new User("default@example.com");
});
}public Optional<String> getStreetName(Long userId) {
return userRepository.findById(userId)
.flatMap(user -> Optional.ofNullable(user.getAddress()))
.flatMap(addr -> Optional.ofNullable(addr.getStreet()))
.flatMap(street -> Optional.ofNullable(street.getName()));
}
class Street {
private String name;
public String getName() { return name; }
}public Optional<Product> findAffordableProduct(Long productId, double budget) {
return productRepository.findById(productId)
.filter(product -> product.getPrice() <= budget)
.filter(product -> product.isInStock())
.filter(product -> !product.isDiscontinued());
}
class Product {
private double price;
private boolean inStock;
private boolean discontinued;
public double getPrice() { return price; }
public boolean isInStock() { return inStock; }
public boolean isDiscontinued() { return discontinued; }
}public List<User> getAllPresentUsers(List<Optional<User>> optionalUsers) {
return optionalUsers.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}public Optional<User> findOldestUser(List<User> users) {
return users.stream()
.max(Comparator.comparingInt(User::getAge));
}
public Optional<String> findLongestName(List<String> names) {
return names.stream()
.max(Comparator.comparingInt(String::length));
}public boolean areEqual(Optional<String> opt1, Optional<String> opt2) {
return opt1.equals(opt2);
}
public boolean bothPresent(Optional<String> opt1, Optional<String> opt2) {
return opt1.isPresent() && opt2.isPresent();
}Problem 53: Monadic Operations
public Optional<String> monadicExample(Optional<Integer> value) {
return value
.filter(v -> v > 0) // Keep only positive
.map(v -> v * 2) // Double it
.filter(v -> v < 100) // Keep only < 100
.map(String::valueOf) // Convert to String
.map(s -> "Result: " + s); // Format
}Problem 54: Safe Operations
public Optional<URL> parseUrl(String urlString) {
try {
return Optional.of(new URL(urlString));
} catch (MalformedURLException e) {
return Optional.empty();
}
}
public Optional<LocalDate> parseDate(String dateString) {
try {
return Optional.of(LocalDate.parse(dateString));
} catch (DateTimeParseException e) {
return Optional.empty();
}
}Problem 55: Fluent Building
public class UserBuilder {
private Optional<String> name = Optional.empty();
private Optional<String> email = Optional.empty();
private Optional<Integer> age = Optional.empty();
public UserBuilder withName(String name) {
this.name = Optional.ofNullable(name);
return this;
}
public UserBuilder withEmail(String email) {
this.email = Optional.ofNullable(email);
return this;
}
public UserBuilder withAge(Integer age) {
this.age = Optional.ofNullable(age);
return this;
}
public Optional<User> build() {
return name.flatMap(n ->
email.flatMap(e ->
age.map(a -> new User(n, e, a))
)
);
}
}Problem 56: Caching with Optional
public class CacheService {
private Map<String, String> cache = new HashMap<>();
public Optional<String> get(String key) {
return Optional.ofNullable(cache.get(key));
}
public String getOrCompute(String key, Supplier<String> supplier) {
return get(key).orElseGet(() -> {
String value = supplier.get();
cache.put(key, value);
return value;
});
}
}Problem 57: Complex Validation
public class ValidationPipeline {
public Optional<String> validateEmail(String email) {
return Optional.ofNullable(email)
.filter(e -> !e.isEmpty())
.filter(e -> e.contains("@"))
.filter(e -> e.indexOf("@") > 0)
.filter(e -> e.indexOf("@") < e.length() - 1);
}
public Optional<Integer> validateAge(Integer age) {
return Optional.ofNullable(age)
.filter(a -> a >= 0)
.filter(a -> a <= 150);
}
public Optional<User> validateAndCreateUser(String name,
String email,
Integer age) {
Optional<String> validEmail = validateEmail(email);
Optional<Integer> validAge = validateAge(age);
return validEmail.flatMap(e ->
validAge.map(a -> new User(name, e, a))
);
}
}Problem 58: Debug-Friendly Optional
public Optional<User> findUserWithLogging(Long id) {
return userRepository.findById(id)
.map(user -> {
logger.info("Found user: {}", user.getName());
return user;
})
.or(() -> {
logger.warn("User not found: {}", id);
return Optional.empty();
});
}Problem 59: Accumulate Multiple Optionals
public Optional<UserProfile> createProfile(Long userId) {
Optional<User> user = userRepository.findById(userId);
Optional<List<String>> hobbies = hobbyService.getHobbies(userId);
Optional<Address> address = addressService.getAddress(userId);
return user.flatMap(u ->
hobbies.flatMap(h ->
address.map(a -> new UserProfile(u, h, a))
)
);
}
class UserProfile {
User user;
List<String> hobbies;
Address address;
public UserProfile(User user, List<String> hobbies, Address address) {
this.user = user;
this.hobbies = hobbies;
this.address = address;
}
}Problem 60: State Transitions
public enum OrderState {
PENDING, CONFIRMED, SHIPPED, DELIVERED
}
public class OrderService {
public Optional<Order> transitionState(Order order, OrderState newState) {
return Optional.of(order)
.filter(o -> isValidTransition(o.getState(), newState))
.map(o -> {
o.setState(newState);
return o;
});
}
private boolean isValidTransition(OrderState current, OrderState next) {
// Define valid transitions
return true;
}
}
class Order {
private OrderState state;
public OrderState getState() { return state; }
public void setState(OrderState state) { this.state = state; }
}Optional.empty() // Empty Optional
Optional.of(value) // ⚠️ Throws NPE if null
Optional.ofNullable(value) // ✅ Safe, recommendedisPresent() // true if value present
isEmpty() // true if empty (Java 11+)get() // ⚠️ Throws exception if empty
orElse(default) // Return default if empty
orElseGet(supplier) // ✅ Lazy default
orElseThrow() // Throw exception
orElseThrow(supplier) // Custom exceptionmap(function) // Transform value
flatMap(function) // Flatten nested Optional
filter(predicate) // Conditional keepifPresent(consumer) // Execute if present
ifPresentOrElse(consumer, runnable) // Java 9+or(supplier) // Alternative Optional (Java 9+)
stream() // Convert to Stream (Java 9+)- Use Optional for return types when absence is a valid result
- Use
ofNullable()instead ofof()unless you're 100% sure value is not null - Chain operations using
map(),flatMap(), andfilter() - Use
orElseGet()for expensive default values - Use
ifPresent()instead ofisPresent() + get() - Return empty Optional instead of null
- Use specialized OptionalInt/Long/Double for primitives
- Don't use Optional for fields
- Don't use Optional for method parameters
- Don't use Optional for collections (return empty collection)
- Don't call
get()without checking - useorElse()family - Don't use
isPresent() + get()- defeats the purpose - Don't return null instead of empty Optional
- Don't create Optional of Optional - use
flatMap()
Need to return possibly absent value?
├─ Collection? → Return Collections.emptyList()/Set()/Map()
├─ Primitive? → Return OptionalInt/Long/Double
└─ Object? → Return Optional<T>
Have potentially null value?
├─ Sure it's not null? → Optional.of(value)
└─ Might be null? → Optional.ofNullable(value) ✅
Need default value?
├─ Simple constant? → orElse(constant)
└─ Expensive computation? → orElseGet(() -> compute()) ✅
Want to transform?
├─ Returns Optional? → flatMap(function)
└─ Returns value? → map(function)
orElse()always evaluates - useorElseGet()for methodsflatMap()avoids nested Optionals - use it for chaining- Primitive Optionals avoid boxing - use IntStream, not Stream
- Don't overuse Optional - simple null checks are sometimes clearer
- ✅ Repository
findById()methods - ✅ Configuration lookups
- ✅ Parsing operations that might fail
- ✅ Search operations
- ✅ Optional method parameters in APIs
- ❌ Class fields
- ❌ Method parameters (use overloading or null)
- ❌ Collections (use empty collection)
- ❌ Getters that can't be null
- ❌ Private helper methods
- Basics: Sections 1-4 (Creation, Checking, Retrieval)
- Core Operations: Sections 5-6 (Transformations, Filtering)
- Practical Usage: Sections 7-10 (Defaults, Actions, Streams, Combining)
- Best Practices: Section 11 ⭐⭐⭐ (Most Important!)
- Anti-Patterns: Section 12 (Learn what NOT to do)
- Real World: Section 13 (Apply to real scenarios)
- Interview Prep: Section 14 (Practice problems)
- Advanced: Section 15 (Master level patterns)
Optional is not about eliminating null - it's about making absence explicit and providing a rich API to handle it functionally. The key is to use it judiciously in the right places (return types) and avoid it in the wrong places (fields, parameters, collections).
Remember: Good code with Optional reads like a pipeline, not a series of null checks.
Happy Coding! 🚀