Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/cc6426829c8865a45f727db7368e94fe to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/cc6426829c8865a45f727db7368e94fe to your computer and use it in GitHub Desktop.
Complete Java Optional Guide with Best Practices

Complete Java Optional Guide with Best Practices

Table of Contents

  1. Optional Basics (Foundation)
  2. Creating Optional Instances
  3. Checking for Values
  4. Retrieving Values
  5. Transforming Values (map & flatMap)
  6. Filtering Values
  7. Default Values & Alternatives
  8. Conditional Actions
  9. Optional with Streams
  10. Combining Optionals
  11. Optional Best Practices ⭐
  12. Common Anti-Patterns to AVOID ❌
  13. Real-World Use Cases
  14. Interview Problems
  15. Advanced Patterns

⭐ Recommended Methods Summary

RECOMMENDED - Use These

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())

⚠️ USE WITH CAUTION

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()

NEVER DO THIS

  • 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 checking isPresent()
  • Don't use isPresent() + get() combo (use functional methods)

1. Optional Basics (Foundation)

What is Optional?

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

Why Optional?

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");
}

Optional vs Null

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
}

2. Creating Optional Instances

Optional.empty()

Problem 2: Create Empty Optional

public Optional<String> getEmptyOptional() {
    return Optional.empty();
}

public Optional<User> findUserById(Long id) {
    // User not found
    return Optional.empty();
}

Optional.of()

⚠️ Warning: Throws NullPointerException if value is null

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
}

Optional.ofNullable() ⭐ RECOMMENDED

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));
}

3. Checking for Values

isPresent()

⚠️ Often overused - prefer functional methods

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);
}

isEmpty() (Java 11+)

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();
}

4. Retrieving Values

get() ⚠️ USE WITH EXTREME CAUTION

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");
}

orElse() ⭐

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"));
}

orElseGet() ⭐ RECOMMENDED for expensive operations

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");
    });
}

orElseThrow() ⭐

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"));
}

5. Transforming Values (map & flatMap)

map() ⭐

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; }
}

flatMap() ⭐

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()));
}

6. Filtering Values

filter() ⭐

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());
}

7. Default Values & Alternatives

orElse() vs orElseGet()

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 creation

or() (Java 9+) ⭐

Problem 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"));
}

8. Conditional Actions

ifPresent() ⭐

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));
}

ifPresentOrElse() (Java 9+) ⭐

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");
}

9. Optional with Streams

stream() (Java 9+) ⭐

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());
}

10. Combining Optionals

Combining Two Optionals

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;
    }
}

11. Optional Best Practices ⭐

✅ DO: Use Optional for Return Types

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));
}

❌ DON'T: Use Optional for Fields

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);
    }
}

❌ DON'T: Use Optional for Parameters

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
}

❌ DON'T: Use Optional for Collections

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();
}

✅ DO: Chain Operations

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");
}

12. Common Anti-Patterns to AVOID ❌

Anti-Pattern 1: isPresent() + get()

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");
}

Anti-Pattern 2: Optional.of(null)

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);
}

Anti-Pattern 3: Calling get() Without Check

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");
}

Anti-Pattern 4: Returning null Instead of Optional

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);
}

Anti-Pattern 5: Nested Optionals

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()));
}

Anti-Pattern 6: Using Optional for Primitive Types

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);
}

13. Real-World Use Cases

Use Case 1: Configuration Management

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);
    }
}

Use Case 2: User Profile Lookup

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()));
    }
}

Use Case 3: API Response Handling

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;
    }
}

Use Case 4: Database Queries

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();
    }
}

Use Case 5: Validation Chains

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"));
    }
}

14. Interview Problems

Problem 41: Find First Even Number

public Optional<Integer> findFirstEven(List<Integer> numbers) {
    return numbers.stream()
                  .filter(n -> n % 2 == 0)
                  .findFirst();
}

Problem 42: Get User Email or Default

public String getUserEmail(Optional<User> user) {
    return user.flatMap(u -> Optional.ofNullable(u.getEmail()))
               .orElse("no-email@example.com");
}

Problem 43: Chain Multiple Optionals

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; }
}

Problem 44: Filter and Transform

public Optional<String> getAdultUserName(Long userId) {
    return userRepository.findById(userId)
            .filter(user -> user.getAge() >= 18)
            .map(User::getName)
            .map(String::toUpperCase);
}

Problem 45: Combine Two Optionals

public Optional<Double> calculateTotal(Optional<Double> price, 
                                       Optional<Integer> quantity) {
    return price.flatMap(p ->
        quantity.map(q -> p * q)
    );
}

Problem 46: Optional with Exception Handling

public Optional<Integer> parseInteger(String value) {
    try {
        return Optional.of(Integer.parseInt(value));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

Problem 47: Default with Supplier

public User getOrCreateUser(Optional<User> user) {
    return user.orElseGet(() -> {
        System.out.println("Creating new user");
        return new User("default@example.com");
    });
}

Problem 48: Multiple Level Navigation

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; }
}

Problem 49: Filter with Multiple Conditions

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; }
}

Problem 50: Convert List of Optionals to List

public List<User> getAllPresentUsers(List<Optional<User>> optionalUsers) {
    return optionalUsers.stream()
            .flatMap(Optional::stream)
            .collect(Collectors.toList());
}

Problem 51: Optional with Comparator

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));
}

Problem 52: Optional Null-Safe Equals

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();
}

15. Advanced Patterns

Pattern 1: Optional Monad Pattern

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
}

Pattern 2: Optional with Try-Catch

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();
    }
}

Pattern 3: Optional Builder Pattern

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))
            )
        );
    }
}

Pattern 4: Optional Cache Pattern

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;
        });
    }
}

Pattern 5: Optional Validation Pipeline

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))
        );
    }
}

Pattern 6: Optional with Logging

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();
            });
}

Pattern 7: Optional Accumulation

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;
    }
}

Pattern 8: Optional State Machine

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; }
}

Quick Reference Card

Creation Methods

Optional.empty()              // Empty Optional
Optional.of(value)            // ⚠️ Throws NPE if null
Optional.ofNullable(value)    // ✅ Safe, recommended

Checking Methods

isPresent()                   // true if value present
isEmpty()                     // true if empty (Java 11+)

Retrieval Methods

get()                         // ⚠️ Throws exception if empty
orElse(default)               // Return default if empty
orElseGet(supplier)           // ✅ Lazy default
orElseThrow()                 // Throw exception
orElseThrow(supplier)         // Custom exception

Transformation Methods

map(function)                 // Transform value
flatMap(function)             // Flatten nested Optional
filter(predicate)             // Conditional keep

Action Methods

ifPresent(consumer)           // Execute if present
ifPresentOrElse(consumer, runnable) // Java 9+

Combination Methods

or(supplier)                  // Alternative Optional (Java 9+)
stream()                      // Convert to Stream (Java 9+)

Summary & Best Practices Checklist

✅ Always Do

  1. Use Optional for return types when absence is a valid result
  2. Use ofNullable() instead of of() unless you're 100% sure value is not null
  3. Chain operations using map(), flatMap(), and filter()
  4. Use orElseGet() for expensive default values
  5. Use ifPresent() instead of isPresent() + get()
  6. Return empty Optional instead of null
  7. Use specialized OptionalInt/Long/Double for primitives

❌ Never Do

  1. Don't use Optional for fields
  2. Don't use Optional for method parameters
  3. Don't use Optional for collections (return empty collection)
  4. Don't call get() without checking - use orElse() family
  5. Don't use isPresent() + get() - defeats the purpose
  6. Don't return null instead of empty Optional
  7. Don't create Optional of Optional - use flatMap()

Decision Tree

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)

Performance Tips

  1. orElse() always evaluates - use orElseGet() for methods
  2. flatMap() avoids nested Optionals - use it for chaining
  3. Primitive Optionals avoid boxing - use IntStream, not Stream
  4. Don't overuse Optional - simple null checks are sometimes clearer

When to Use Optional

  • ✅ Repository findById() methods
  • ✅ Configuration lookups
  • ✅ Parsing operations that might fail
  • ✅ Search operations
  • ✅ Optional method parameters in APIs

When NOT to Use Optional

  • ❌ Class fields
  • ❌ Method parameters (use overloading or null)
  • ❌ Collections (use empty collection)
  • ❌ Getters that can't be null
  • ❌ Private helper methods

Recommended Reading Order

  1. Basics: Sections 1-4 (Creation, Checking, Retrieval)
  2. Core Operations: Sections 5-6 (Transformations, Filtering)
  3. Practical Usage: Sections 7-10 (Defaults, Actions, Streams, Combining)
  4. Best Practices: Section 11 ⭐⭐⭐ (Most Important!)
  5. Anti-Patterns: Section 12 (Learn what NOT to do)
  6. Real World: Section 13 (Apply to real scenarios)
  7. Interview Prep: Section 14 (Practice problems)
  8. Advanced: Section 15 (Master level patterns)

Final Thoughts

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! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment