Your findUser(id) method might not find a user. Returning null leads to NullPointerException when callers forget to check. Optional makes the "might be absent" explicit - callers must handle both cases.

Create Optional values

Wrap values in Optional.

CreatingOptional.java
// Creating and checking Optional values
// Concept: Optional - container for potentially absent value

import java.util.Optional;

public class CreatingOptional {
    public static void main(String[] args) {
        // Create Optional with value
        Optional<String> present = Optional.of("Hello");
        System.out.println("Present: " + present);
        
        // Create Optional from nullable value
        String nullable = ;
        Optional<String> maybe1 = Optional.ofNullable(nullable);
        System.out.println("Maybe 1: " + maybe1);
        
        nullable = null;
        Optional<String> maybe2 = Optional.ofNullable(nullable);
        System.out.println("Maybe 2: " + maybe2);
        
        
        // Create empty Optional
        Optional<String> empty = Optional.empty();
        System.out.println("Empty: " + empty);
        
        // Check if value is present
        System.out.println("\nChecking presence:");
        System.out.println("present.isPresent(): " + present.isPresent());
        System.out.println("empty.isPresent(): " + empty.isPresent());
        
        // isEmpty (Java 11+)
        System.out.println("\npresent.isEmpty(): " + present.isEmpty());
        System.out.println("empty.isEmpty(): " + empty.isEmpty());
        
        // Get value if present
        if (present.isPresent()) {
            String value = present.get();
            System.out.println("\nValue: " + value);
        }
        
        if (empty.isPresent()) {
            System.out.println("This won't print");
        } else {
            System.out.println("Empty has no value");
        }
        
        // Example with numbers
        Optional<Integer> num1 = Optional.of(42);
        Optional<Integer> num2 = Optional.ofNullable(null);
        
        System.out.println("\nNumbers:");
        System.out.println("num1: " + num1);
        System.out.println("num2: " + num2);
        System.out.println("num1 present: " + num1.isPresent());
        System.out.println("num2 present: " + num2.isPresent());
        
    }
}
// Creating and checking Optional values
// Concept: Optional - container for potentially absent value

import java.util.Optional;

public class CreatingOptional {
    public static void main(String[] args) {
        // Create Optional with value
        Optional<String> present = Optional.of("Hello");
        System.out.println("Present: " + present);
        
        // Create Optional from nullable value
        String nullable = ;
        Optional<String> maybe1 = Optional.ofNullable(nullable);
        System.out.println("Maybe 1: " + maybe1);
        
        nullable = null;
        Optional<String> maybe2 = Optional.ofNullable(nullable);
        System.out.println("Maybe 2: " + maybe2);
        
        
        // Create empty Optional
        Optional<String> empty = Optional.empty();
        System.out.println("Empty: " + empty);
        
        // Check if value is present
        System.out.println("\nChecking presence:");
        System.out.println("present.isPresent(): " + present.isPresent());
        System.out.println("empty.isPresent(): " + empty.isPresent());
        
        // isEmpty (Java 11+)
        System.out.println("\npresent.isEmpty(): " + present.isEmpty());
        System.out.println("empty.isEmpty(): " + empty.isEmpty());
        
        // Get value if present
        if (present.isPresent()) {
            String value = present.get();
            System.out.println("\nValue: " + value);
        }
        
        if (empty.isPresent()) {
            System.out.println("This won't print");
        } else {
            System.out.println("Empty has no value");
        }
        
        // Example with numbers
        Optional<Integer> num1 = Optional.of(42);
        Optional<Integer> num2 = Optional.ofNullable(null);
        
        System.out.println("\nNumbers:");
        System.out.println("num1: " + num1);
        System.out.println("num2: " + num2);
        System.out.println("num1 present: " + num1.isPresent());
        System.out.println("num2 present: " + num2.isPresent());
        
    }
}
// Creating and checking Optional values
// Concept: Optional - container for potentially absent value

import java.util.Optional;

public class CreatingOptional {
    public static void main(String[] args) {
        // Create Optional with value
        Optional<String> present = Optional.of("Hello");
        System.out.println("Present: " + present);
        
        // Create Optional from nullable value
        String nullable = ;
        Optional<String> maybe1 = Optional.ofNullable(nullable);
        System.out.println("Maybe 1: " + maybe1);
        
        nullable = null;
        Optional<String> maybe2 = Optional.ofNullable(nullable);
        System.out.println("Maybe 2: " + maybe2);
        
        
        // Create empty Optional
        Optional<String> empty = Optional.empty();
        System.out.println("Empty: " + empty);
        
        // Check if value is present
        System.out.println("\nChecking presence:");
        System.out.println("present.isPresent(): " + present.isPresent());
        System.out.println("empty.isPresent(): " + empty.isPresent());
        
        // isEmpty (Java 11+)
        System.out.println("\npresent.isEmpty(): " + present.isEmpty());
        System.out.println("empty.isEmpty(): " + empty.isEmpty());
        
        // Get value if present
        if (present.isPresent()) {
            String value = present.get();
            System.out.println("\nValue: " + value);
        }
        
        if (empty.isPresent()) {
            System.out.println("This won't print");
        } else {
            System.out.println("Empty has no value");
        }
        
        // Example with numbers
        Optional<Integer> num1 = Optional.of(42);
        Optional<Integer> num2 = Optional.ofNullable(null);
        
        System.out.println("\nNumbers:");
        System.out.println("num1: " + num1);
        System.out.println("num2: " + num2);
        System.out.println("num1 present: " + num1.isPresent());
        System.out.println("num2 present: " + num2.isPresent());
        
    }
}

Optional.of() for non-null, Optional.ofNullable() for maybe-null, Optional.empty() for absent.

Optional Container that may or may not contain a value. Makes null explicit.

Check and use values

Handle present and absent cases.

IfPresent.java
// ifPresent and ifPresentOrElse
// Concept: ifPresent - execute action if value exists

import java.util.Optional;

public class IfPresent {
    public static void main(String[] args) {
        // ifPresent - do something if value exists
        Optional<String> name = ;
        
        System.out.println("Using ifPresent:");
        name.ifPresent(value -> System.out.println("  Hello, " + value));
        
        Optional<String> empty = Optional.empty();
        empty.ifPresent(value -> System.out.println("  This won't print"));
        
        
        // ifPresentOrElse (Java 9+)
        System.out.println("\nUsing ifPresentOrElse:");
        
        name.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        empty.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        // Process multiple optionals
        Optional<Integer>[] scores = new Optional[] {
            Optional.of(85),
            Optional.empty(),
            Optional.of(92),
            Optional.of(78)
        };
        
        System.out.println("\nProcessing scores:");
        for (int i = 0; i < scores.length; i++) {
            int index = i;
            scores[i].ifPresent(score -> 
                System.out.println("  Score " + index + ": " + score)
            );
        }
        
        // Accumulate values
        System.out.println("\nAccumulating:");
        
        int[] sum = {0};
        int[] count = {0};
        
        for (Optional<Integer> score : scores) {
            score.ifPresent(value -> {
                sum[0] += value;
                count[0]++;
            });
        }
        
        if (count[0] > 0) {
            double average = (double) sum[0] / count[0];
            System.out.printf("  Average: %.1f%n", average);
        }
        
        // Log if missing
        System.out.println("\nLogging:");
        
        Optional<String> config = Optional.empty();
        
        config.ifPresentOrElse(
            value -> System.out.println("  Config: " + value),
            () -> System.out.println("  WARNING: Config not found")
        );
        
    }
}
// ifPresent and ifPresentOrElse
// Concept: ifPresent - execute action if value exists

import java.util.Optional;

public class IfPresent {
    public static void main(String[] args) {
        // ifPresent - do something if value exists
        Optional<String> name = ;
        
        System.out.println("Using ifPresent:");
        name.ifPresent(value -> System.out.println("  Hello, " + value));
        
        Optional<String> empty = Optional.empty();
        empty.ifPresent(value -> System.out.println("  This won't print"));
        
        
        // ifPresentOrElse (Java 9+)
        System.out.println("\nUsing ifPresentOrElse:");
        
        name.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        empty.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        // Process multiple optionals
        Optional<Integer>[] scores = new Optional[] {
            Optional.of(85),
            Optional.empty(),
            Optional.of(92),
            Optional.of(78)
        };
        
        System.out.println("\nProcessing scores:");
        for (int i = 0; i < scores.length; i++) {
            int index = i;
            scores[i].ifPresent(score -> 
                System.out.println("  Score " + index + ": " + score)
            );
        }
        
        // Accumulate values
        System.out.println("\nAccumulating:");
        
        int[] sum = {0};
        int[] count = {0};
        
        for (Optional<Integer> score : scores) {
            score.ifPresent(value -> {
                sum[0] += value;
                count[0]++;
            });
        }
        
        if (count[0] > 0) {
            double average = (double) sum[0] / count[0];
            System.out.printf("  Average: %.1f%n", average);
        }
        
        // Log if missing
        System.out.println("\nLogging:");
        
        Optional<String> config = Optional.empty();
        
        config.ifPresentOrElse(
            value -> System.out.println("  Config: " + value),
            () -> System.out.println("  WARNING: Config not found")
        );
        
    }
}
// ifPresent and ifPresentOrElse
// Concept: ifPresent - execute action if value exists

import java.util.Optional;

public class IfPresent {
    public static void main(String[] args) {
        // ifPresent - do something if value exists
        Optional<String> name = ;
        
        System.out.println("Using ifPresent:");
        name.ifPresent(value -> System.out.println("  Hello, " + value));
        
        Optional<String> empty = Optional.empty();
        empty.ifPresent(value -> System.out.println("  This won't print"));
        
        
        // ifPresentOrElse (Java 9+)
        System.out.println("\nUsing ifPresentOrElse:");
        
        name.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        empty.ifPresentOrElse(
            value -> System.out.println("  Found: " + value),
            () -> System.out.println("  Not found")
        );
        
        // Process multiple optionals
        Optional<Integer>[] scores = new Optional[] {
            Optional.of(85),
            Optional.empty(),
            Optional.of(92),
            Optional.of(78)
        };
        
        System.out.println("\nProcessing scores:");
        for (int i = 0; i < scores.length; i++) {
            int index = i;
            scores[i].ifPresent(score -> 
                System.out.println("  Score " + index + ": " + score)
            );
        }
        
        // Accumulate values
        System.out.println("\nAccumulating:");
        
        int[] sum = {0};
        int[] count = {0};
        
        for (Optional<Integer> score : scores) {
            score.ifPresent(value -> {
                sum[0] += value;
                count[0]++;
            });
        }
        
        if (count[0] > 0) {
            double average = (double) sum[0] / count[0];
            System.out.printf("  Average: %.1f%n", average);
        }
        
        // Log if missing
        System.out.println("\nLogging:");
        
        Optional<String> config = Optional.empty();
        
        config.ifPresentOrElse(
            value -> System.out.println("  Config: " + value),
            () -> System.out.println("  WARNING: Config not found")
        );
        
    }
}

ifPresent() runs code only when value exists. Avoids null checks.

Provide defaults

Get value or use fallback.

Defaults.java
// orElse, orElseGet, orElseThrow
// Concept: default values - providing fallbacks

import java.util.Optional;

public class Defaults {
    public static void main(String[] args) {
        // orElse - provide default value
        Optional<String> name = ;
        Optional<String> empty = Optional.empty();
        
        String result1 = name.orElse("Unknown");
        String result2 = empty.orElse("Unknown");
        
        System.out.println("orElse:");
        System.out.println("  name: " + result1);
        System.out.println("  empty: " + result2);
        
        
        // orElseGet - compute default lazily
        System.out.println("\norElseGet:");
        
        String r1 = name.orElseGet(() -> {
            System.out.println("  Computing default...");
            return "Generated";
        });
        System.out.println("  Result: " + r1);
        
        String r2 = empty.orElseGet(() -> {
            System.out.println("  Computing default...");
            return "Generated";
        });
        System.out.println("  Result: " + r2);
        
        // orElse vs orElseGet difference
        System.out.println("\nDifference:");
        
        System.out.println("With orElse:");
        String x1 = name.orElse(expensiveDefault());
        
        System.out.println("\nWith orElseGet:");
        String x2 = name.orElseGet(() -> expensiveDefault());
        
        // orElseThrow - require value
        System.out.println("\norElseThrow:");
        
        try {
            String value = name.orElseThrow();
            System.out.println("  Got value: " + value);
        } catch (Exception e) {
            System.out.println("  Error: " + e);
        }
        
        try {
            String value = empty.orElseThrow();
            System.out.println("  Got value: " + value);
        } catch (Exception e) {
            System.out.println("  Error: " + e.getClass().getSimpleName());
        }
        
        // orElseThrow with custom exception
        try {
            String value = empty.orElseThrow(() -> 
                new IllegalArgumentException("Value required")
            );
        } catch (IllegalArgumentException e) {
            System.out.println("  Custom error: " + e.getMessage());
        }
        
        // Practical examples
        System.out.println("\nPractical:");
        
        Optional<Integer> port = Optional.empty();
        int serverPort = port.orElse(8080);
        System.out.println("  Server port: " + serverPort);
        
        Optional<String> env = Optional.ofNullable(null);
        String environment = env.orElseGet(() -> {
            System.out.println("  Reading from config...");
            return "development";
        });
        System.out.println("  Environment: " + environment);
        
        Optional<String> apiKey = Optional.empty();
        try {
            String key = apiKey.orElseThrow(() -> 
                new RuntimeException("API key not configured")
            );
        } catch (RuntimeException e) {
            System.out.println("  " + e.getMessage());
        }
    }
    
    static String expensiveDefault() {
        System.out.println("  Expensive computation!");
        return "Default";
    }
}
// orElse, orElseGet, orElseThrow
// Concept: default values - providing fallbacks

import java.util.Optional;

public class Defaults {
    public static void main(String[] args) {
        // orElse - provide default value
        Optional<String> name = ;
        Optional<String> empty = Optional.empty();
        
        String result1 = name.orElse("Unknown");
        String result2 = empty.orElse("Unknown");
        
        System.out.println("orElse:");
        System.out.println("  name: " + result1);
        System.out.println("  empty: " + result2);
        
        
        // orElseGet - compute default lazily
        System.out.println("\norElseGet:");
        
        String r1 = name.orElseGet(() -> {
            System.out.println("  Computing default...");
            return "Generated";
        });
        System.out.println("  Result: " + r1);
        
        String r2 = empty.orElseGet(() -> {
            System.out.println("  Computing default...");
            return "Generated";
        });
        System.out.println("  Result: " + r2);
        
        // orElse vs orElseGet difference
        System.out.println("\nDifference:");
        
        System.out.println("With orElse:");
        String x1 = name.orElse(expensiveDefault());
        
        System.out.println("\nWith orElseGet:");
        String x2 = name.orElseGet(() -> expensiveDefault());
        
        // orElseThrow - require value
        System.out.println("\norElseThrow:");
        
        try {
            String value = name.orElseThrow();
            System.out.println("  Got value: " + value);
        } catch (Exception e) {
            System.out.println("  Error: " + e);
        }
        
        try {
            String value = empty.orElseThrow();
            System.out.println("  Got value: " + value);
        } catch (Exception e) {
            System.out.println("  Error: " + e.getClass().getSimpleName());
        }
        
        // orElseThrow with custom exception
        try {
            String value = empty.orElseThrow(() -> 
                new IllegalArgumentException("Value required")
            );
        } catch (IllegalArgumentException e) {
            System.out.println("  Custom error: " + e.getMessage());
        }
        
        // Practical examples
        System.out.println("\nPractical:");
        
        Optional<Integer> port = Optional.empty();
        int serverPort = port.orElse(8080);
        System.out.println("  Server port: " + serverPort);
        
        Optional<String> env = Optional.ofNullable(null);
        String environment = env.orElseGet(() -> {
            System.out.println("  Reading from config...");
            return "development";
        });
        System.out.println("  Environment: " + environment);
        
        Optional<String> apiKey = Optional.empty();
        try {
            String key = apiKey.orElseThrow(() -> 
                new RuntimeException("API key not configured")
            );
        } catch (RuntimeException e) {
            System.out.println("  " + e.getMessage());
        }
    }
    
    static String expensiveDefault() {
        System.out.println("  Expensive computation!");
        return "Default";
    }
}

orElse() provides default. orElseGet() computes default lazily.

orElse Get value if present, otherwise use default: `opt.orElse("default")`.

Transform with map

Apply function to Optional's value.

MapFlatmap.java
// map and flatMap transformations
// Concept: transformation - converting Optional values

import java.util.Optional;

public class MapFlatmap {
    public static void main(String[] args) {
        // map - transform value if present
        Optional<String> name = ;
        
        Optional<String> upper = name.map(s -> s.toUpperCase());
        System.out.println("Original: " + name);
        System.out.println("Uppercase: " + upper);
        
        Optional<Integer> length = name.map(s -> s.length());
        System.out.println("Length: " + length);
        
        // map on empty Optional
        Optional<String> empty = Optional.empty();
        Optional<String> result = empty.map(s -> s.toUpperCase());
        System.out.println("\nEmpty mapped: " + result);
        
        
        // Chain multiple maps
        System.out.println("\nChained maps:");
        
        Optional<String> processed = Optional.of("  hello  ")
            .map(s -> s.trim())
            .map(s -> s.toUpperCase())
            .map(s -> s + "!");
        
        System.out.println("Result: " + processed.orElse(""));
        
        // flatMap - when function returns Optional
        Optional<String> userId = Optional.of("123");
        
        Optional<User> user = userId.flatMap(id -> findUser(id));
        System.out.println("\nUser: " + user);
        
        Optional<String> email = userId
            .flatMap(id -> findUser(id))
            .map(u -> u.email);
        System.out.println("Email: " + email);
        
        // map vs flatMap
        System.out.println("\nmap vs flatMap:");
        
        // map wraps result in Optional
        Optional<Optional<User>> nested = userId.map(id -> findUser(id));
        System.out.println("map result: " + nested);
        
        // flatMap doesn't wrap
        Optional<User> flat = userId.flatMap(id -> findUser(id));
        System.out.println("flatMap result: " + flat);
        
        // Practical transformation pipeline
        System.out.println("\nPipeline:");
        
        Optional<String> input = Optional.of("42");
        
        Optional<String> output = input
            .map(s -> Integer.parseInt(s))
            .map(n -> n * 2)
            .map(n -> "Result: " + n);
        
        output.ifPresent(System.out::println);
        
        // Safe navigation
        Optional<Integer> price = Optional.of("Product123")
            .flatMap(id -> findProduct(id))
            .map(p -> p.price);
        
        System.out.println("\nProduct price: " + price.orElse(0));
        
    }
    
    static Optional<User> findUser(String id) {
        if (id.equals("123")) {
            return Optional.of(new User("Alice", "alice@example.com"));
        }
        return Optional.empty();
    }
    
    static Optional<Product> findProduct(String id) {
        if (id.equals("Product123")) {
            return Optional.of(new Product("Laptop", 999));
        }
        return Optional.empty();
    }
    
    static class User {
        String name;
        String email;
        User(String name, String email) {
            this.name = name;
            this.email = email;
        }
        public String toString() {
            return "User(" + name + ", " + email + ")";
        }
    }
    
    static class Product {
        String name;
        int price;
        Product(String name, int price) {
            this.name = name;
            this.price = price;
        }
    }
}
// map and flatMap transformations
// Concept: transformation - converting Optional values

import java.util.Optional;

public class MapFlatmap {
    public static void main(String[] args) {
        // map - transform value if present
        Optional<String> name = ;
        
        Optional<String> upper = name.map(s -> s.toUpperCase());
        System.out.println("Original: " + name);
        System.out.println("Uppercase: " + upper);
        
        Optional<Integer> length = name.map(s -> s.length());
        System.out.println("Length: " + length);
        
        // map on empty Optional
        Optional<String> empty = Optional.empty();
        Optional<String> result = empty.map(s -> s.toUpperCase());
        System.out.println("\nEmpty mapped: " + result);
        
        
        // Chain multiple maps
        System.out.println("\nChained maps:");
        
        Optional<String> processed = Optional.of("  hello  ")
            .map(s -> s.trim())
            .map(s -> s.toUpperCase())
            .map(s -> s + "!");
        
        System.out.println("Result: " + processed.orElse(""));
        
        // flatMap - when function returns Optional
        Optional<String> userId = Optional.of("123");
        
        Optional<User> user = userId.flatMap(id -> findUser(id));
        System.out.println("\nUser: " + user);
        
        Optional<String> email = userId
            .flatMap(id -> findUser(id))
            .map(u -> u.email);
        System.out.println("Email: " + email);
        
        // map vs flatMap
        System.out.println("\nmap vs flatMap:");
        
        // map wraps result in Optional
        Optional<Optional<User>> nested = userId.map(id -> findUser(id));
        System.out.println("map result: " + nested);
        
        // flatMap doesn't wrap
        Optional<User> flat = userId.flatMap(id -> findUser(id));
        System.out.println("flatMap result: " + flat);
        
        // Practical transformation pipeline
        System.out.println("\nPipeline:");
        
        Optional<String> input = Optional.of("42");
        
        Optional<String> output = input
            .map(s -> Integer.parseInt(s))
            .map(n -> n * 2)
            .map(n -> "Result: " + n);
        
        output.ifPresent(System.out::println);
        
        // Safe navigation
        Optional<Integer> price = Optional.of("Product123")
            .flatMap(id -> findProduct(id))
            .map(p -> p.price);
        
        System.out.println("\nProduct price: " + price.orElse(0));
        
    }
    
    static Optional<User> findUser(String id) {
        if (id.equals("123")) {
            return Optional.of(new User("Alice", "alice@example.com"));
        }
        return Optional.empty();
    }
    
    static Optional<Product> findProduct(String id) {
        if (id.equals("Product123")) {
            return Optional.of(new Product("Laptop", 999));
        }
        return Optional.empty();
    }
    
    static class User {
        String name;
        String email;
        User(String name, String email) {
            this.name = name;
            this.email = email;
        }
        public String toString() {
            return "User(" + name + ", " + email + ")";
        }
    }
    
    static class Product {
        String name;
        int price;
        Product(String name, int price) {
            this.name = name;
            this.price = price;
        }
    }
}
// map and flatMap transformations
// Concept: transformation - converting Optional values

import java.util.Optional;

public class MapFlatmap {
    public static void main(String[] args) {
        // map - transform value if present
        Optional<String> name = ;
        
        Optional<String> upper = name.map(s -> s.toUpperCase());
        System.out.println("Original: " + name);
        System.out.println("Uppercase: " + upper);
        
        Optional<Integer> length = name.map(s -> s.length());
        System.out.println("Length: " + length);
        
        // map on empty Optional
        Optional<String> empty = Optional.empty();
        Optional<String> result = empty.map(s -> s.toUpperCase());
        System.out.println("\nEmpty mapped: " + result);
        
        
        // Chain multiple maps
        System.out.println("\nChained maps:");
        
        Optional<String> processed = Optional.of("  hello  ")
            .map(s -> s.trim())
            .map(s -> s.toUpperCase())
            .map(s -> s + "!");
        
        System.out.println("Result: " + processed.orElse(""));
        
        // flatMap - when function returns Optional
        Optional<String> userId = Optional.of("123");
        
        Optional<User> user = userId.flatMap(id -> findUser(id));
        System.out.println("\nUser: " + user);
        
        Optional<String> email = userId
            .flatMap(id -> findUser(id))
            .map(u -> u.email);
        System.out.println("Email: " + email);
        
        // map vs flatMap
        System.out.println("\nmap vs flatMap:");
        
        // map wraps result in Optional
        Optional<Optional<User>> nested = userId.map(id -> findUser(id));
        System.out.println("map result: " + nested);
        
        // flatMap doesn't wrap
        Optional<User> flat = userId.flatMap(id -> findUser(id));
        System.out.println("flatMap result: " + flat);
        
        // Practical transformation pipeline
        System.out.println("\nPipeline:");
        
        Optional<String> input = Optional.of("42");
        
        Optional<String> output = input
            .map(s -> Integer.parseInt(s))
            .map(n -> n * 2)
            .map(n -> "Result: " + n);
        
        output.ifPresent(System.out::println);
        
        // Safe navigation
        Optional<Integer> price = Optional.of("Product123")
            .flatMap(id -> findProduct(id))
            .map(p -> p.price);
        
        System.out.println("\nProduct price: " + price.orElse(0));
        
    }
    
    static Optional<User> findUser(String id) {
        if (id.equals("123")) {
            return Optional.of(new User("Alice", "alice@example.com"));
        }
        return Optional.empty();
    }
    
    static Optional<Product> findProduct(String id) {
        if (id.equals("Product123")) {
            return Optional.of(new Product("Laptop", 999));
        }
        return Optional.empty();
    }
    
    static class User {
        String name;
        String email;
        User(String name, String email) {
            this.name = name;
            this.email = email;
        }
        public String toString() {
            return "User(" + name + ", " + email + ")";
        }
    }
    
    static class Product {
        String name;
        int price;
        Product(String name, int price) {
            this.name = name;
            this.price = price;
        }
    }
}

map() transforms value if present. flatMap() for nested Optionals.

map Transform Optional: `opt.map(String::toUpperCase)`. Returns Optional.

Optional return types

Use Optional for methods that might not return a value.

ReturnTypes.java
// Optional in return types
// Concept: pattern - using Optional for method returns

import java.util.Optional;
import java.util.HashMap;
import java.util.Map;

public class ReturnTypes {
    // Repository with Optional returns
    static class UserRepository {
        private Map<Integer, User> users = new HashMap<>();
        
        UserRepository() {
            users.put(1, new User(1, "Alice", "alice@example.com"));
            users.put(2, new User(2, "Bob", "bob@example.com"));
        }
        
        // Return Optional instead of null
        Optional<User> findById(int id) {
            return Optional.ofNullable(users.get(id));
        }
        
        Optional<User> findByEmail(String email) {
            return users.values().stream()
                .filter(u -> u.email.equals(email))
                .findFirst();
        }
    }
    
    static class User {
        int id;
        String name;
        String email;
        
        User(int id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
        
        public String toString() {
            return "User(" + id + ", " + name + ")";
        }
    }
    
    public static void main(String[] args) {
        UserRepository repo = new UserRepository();
        
        // Call methods returning Optional
        System.out.println("Finding users:");
        
        Optional<User> user1 = repo.findById(1);
        user1.ifPresent(u -> System.out.println("  Found: " + u));
        
        Optional<User> user99 = repo.findById(99);
        user99.ifPresentOrElse(
            u -> System.out.println("  Found: " + u),
            () -> System.out.println("  User 99 not found")
        );
        
        
        // Chain Optional operations
        System.out.println("\nChained operations:");
        
        String userName = repo.findById(1)
            .map(u -> u.name)
            .orElse("Unknown");
        System.out.println("  User 1 name: " + userName);
        
        String email = repo.findById(99)
            .map(u -> u.email)
            .orElse("no-email@example.com");
        System.out.println("  User 99 email: " + email);
        
        // Find by email
        System.out.println("\nFind by email:");
        
        repo.findByEmail("alice@example.com")
            .ifPresent(u -> System.out.println("  Found: " + u));
        
        repo.findByEmail("unknown@example.com")
            .ifPresent(u -> System.out.println("  Won't print"));
        
        // Configuration service
        System.out.println("\nConfiguration:");
        
        ConfigService config = new ConfigService();
        
        int port = config.getPort().orElse(8080);
        System.out.println("  Port: " + port);
        
        String host = config.getHost().orElse("localhost");
        System.out.println("  Host: " + host);
        
        config.getApiKey().ifPresentOrElse(
            key -> System.out.println("  API key: " + key),
            () -> System.out.println("  WARNING: No API key configured")
        );
        
        // Error handling
        System.out.println("\nWith error handling:");
        
        try {
            User user = repo.findById(99)
                .orElseThrow(() -> new RuntimeException("User not found"));
        } catch (RuntimeException e) {
            System.out.println("  Error: " + e.getMessage());
        }
        
        // Transform to different type
        Optional<String> welcomeMsg = repo.findById(2)
            .map(u -> "Welcome, " + u.name + "!");
        
        System.out.println("\n" + welcomeMsg.orElse("Welcome, Guest!"));
    }
    
    static class ConfigService {
        Optional<Integer> getPort() {
            return Optional.of(9090);
        }
        
        Optional<String> getHost() {
            return Optional.empty();  // Not configured
        }
        
        Optional<String> getApiKey() {
            return Optional.empty();  // Not configured
        }
    }
}

Return Optional<T> instead of nullable T. Caller knows to check.

Exercise: Practical.java

Refactor a service layer to use Optional