Optional — Null Safety

Optional — NullPointerException থেকে বাঁচার type-safe উপায়

Read: ~30 min Intermediate 5 practice problems Live code runner

1. The Billion-Dollar Mistake

null is the default absence marker in Java — and the source of countless NullPointerExceptions in production. Tony Hoare, who introduced it in 1965, calls it his "billion-dollar mistake." Java 8 introduced Optional<T>, a container that either holds a value or is empty, making absence visible in the type system.

Java-তে null — default "কিছু নেই" signal — production-এ অসংখ্য NullPointerException-এর উৎস। Tony Hoare ১৯৬৫ সালে null-কে তাঁর "billion-dollar mistake" বলেছিলেন। Java 8 দিয়েছে Optional<T> — একটি container যা হয় value রাখে নয়তো empty; এটি type-system-এ "absence" দৃশ্যমান করে।

2. Creating an Optional

Use Optional.of(x) for a known-non-null value, Optional.empty() for nothing, and Optional.ofNullable(x) when x may itself be null.

Optional.of(x) — non-null value; Optional.empty() — কিছু নেই; Optional.ofNullable(x) — x নিজেই null হতে পারে এমন ক্ষেত্রে।
Main.java
import java.util.*;

class Main {
    public static void main(String[] args) {
        Optional<String> a = Optional.of("ABCL TECH");
        Optional<String> b = Optional.empty();
        String nullable = System.getenv("DOES_NOT_EXIST");    // returns null
        Optional<String> c = Optional.ofNullable(nullable);

        System.out.println("a = " + a);
        System.out.println("b = " + b);
        System.out.println("c = " + c);
    }
}
Danger: Optional.of(null) throws NullPointerException immediately. If the value might be null, always use ofNullable.

Optional.of(null) সরাসরি NullPointerException ছুড়ে দেয় — null হতে পারে এমন value-তে সব সময় ofNullable।

3. Consuming an Optional

Prefer ifPresent, orElse, orElseGet, and orElseThrow. Avoid .get() without an isPresent check — that's basically null with extra steps.

ifPresent, orElse, orElseGet, orElseThrow ব্যবহার করুন। isPresent check ছাড়া .get() এড়িয়ে চলুন — সেটি আসলে null-ই, শুধু একটু ঘুরিয়ে।
Main.java
import java.util.*;

class Main {
    static Optional<String> findUser(int id) {
        return id == 1 ? Optional.of("Arif") : Optional.empty();
    }

    public static void main(String[] args) {
        findUser(1).ifPresent(n -> System.out.println("found: " + n));
        findUser(2).ifPresent(n -> System.out.println("never runs"));

        String fallback = findUser(2).orElse("(anonymous)");
        System.out.println("fallback = " + fallback);

        // orElseGet: compute default lazily
        String lazyFallback = findUser(2).orElseGet(() -> "computed-default");
        System.out.println("lazy  = " + lazyFallback);

        // orElseThrow
        try {
            findUser(2).orElseThrow(() -> new RuntimeException("not found"));
        } catch (RuntimeException ex) {
            System.out.println("caught: " + ex.getMessage());
        }
    }
}

4. map & flatMap on Optional

Just like streams, Optional has map (transform the inner value if present) and flatMap (when the transformation itself returns an Optional). Chaining these avoids nested if (x != null).

Stream-এর মতো Optional-এও map (ভেতরের value transform) ও flatMap (যখন transform নিজেই Optional দেয়) আছে। Chain করলে nested if (x != null) লাগে না।
Optional — value or empty; operations pass through Optional["Arif"] .map(String::length) Optional[4] Optional.empty() .map(String::length) Optional.empty() Figure 30.1 — empty Optional-এ map/flatMap chain করলেও NPE হয় না।
Main.java
import java.util.*;

class Main {
    record Address(String city) {}
    record User(String name, Optional<Address> addr) {}

    public static void main(String[] args) {
        User u1 = new User("Arif", Optional.of(new Address("Dhaka")));
        User u2 = new User("Rina", Optional.empty());

        // Without Optional, this would be 4 nested null checks
        String c1 = u1.addr().map(Address::city).orElse("?");
        String c2 = u2.addr().map(Address::city).orElse("?");

        System.out.println("u1 city = " + c1);
        System.out.println("u2 city = " + c2);

        // flatMap — when the mapping itself returns Optional
        Optional<String> shout = Optional.of("abcl")
            .flatMap(s -> Optional.of(s.toUpperCase()));
        System.out.println("shout = " + shout);
    }
}

5. Anti-Patterns to Avoid

⚠️ Don't Do This

  • opt.get() without isPresent
  • Taking Optional as a method parameter
  • Storing Optional in a field or collection
  • Optional.of(x) when x may be null
  • opt.orElse(expensiveCall()) — eager

✅ Do This Instead

  • opt.ifPresent(...) / orElse(...) / orElseThrow(...)
  • Use plain parameter; let null-handling stay at the call site
  • Use a sentinel value or a primitive; keep Optional for return types
  • Optional.ofNullable(x)
  • opt.orElseGet(() -> expensiveCall())
Optional-এর নিয়ম: return type হিসেবে ব্যবহার করুন, parameter হিসেবে নয়। Field/collection-এও সাধারণত রাখবেন না; get()-এর বদলে ifPresent/orElse; expensive default হলে orElseGet।

6. Vocabulary

MethodMeaningবাংলায়
Optional.of(x)Non-null value; throws NPE if x is null.Non-null value; null হলে NPE।
Optional.ofNullable(x)Empty if x is null, else present.x null হলে empty।
isPresent / isEmptyCheck presence/absence.Presence/absence check।
ifPresent(c)Run consumer if value present.Value থাকলে consumer চালায়।
orElse(def)Eager default.Default value (eager)।
orElseGet(sup)Lazy default (Supplier).Lazy default।
orElseThrow(sup)Throw on empty.Empty হলে exception।
map / flatMapTransform inside; flat avoids nested Optional.ভেতরে transform; flatMap nested Optional এড়ায়।

7. Practice Problems

  1. Return an Optional<String> that is empty when input is null, else contains the trimmed string.
    input null হলে empty, নয়তো trimmed string-বিশিষ্ট Optional<String> ফেরত দিন।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        static Optional<String> cleaned(String s) {
            return Optional.ofNullable(s).map(String::trim);
        }
        public static void main(String[] args) {
            System.out.println(cleaned("   hi  "));
            System.out.println(cleaned(null));
        }
    }
  2. Given an Optional<Integer>, print double the value if present, else "no value".
    Optional<Integer>-এ value থাকলে দ্বিগুণ প্রিন্ট করুন, নয়তো "no value"।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        public static void main(String[] args) {
            Optional<Integer> x = Optional.of(7);
            System.out.println(x.map(n -> n * 2).map(String::valueOf).orElse("no value"));
    
            Optional<Integer> y = Optional.empty();
            System.out.println(y.map(n -> n * 2).map(String::valueOf).orElse("no value"));
        }
    }
  3. Why is orElseGet preferable to orElse for expensive defaults?
    Expensive default-এর জন্য orElse-এর বদলে orElseGet কেন ভালো?
    ✨ Show Answer

    Answer: orElse(x) evaluates x eagerly — before checking whether the Optional is present — so any expensive computation runs even when the value was present and the default isn't needed. orElseGet(Supplier) evaluates the supplier only when the Optional is empty. For expensive defaults (a database call, heavy object construction), always prefer orElseGet.

    orElse(x) x-কে আগেই evaluate করে — Optional-এ value থাকলেও expensive computation চলে যায়। orElseGet(Supplier) শুধু empty হলেই supplier চালায়। DB call-এর মতো ভারী default-এর জন্য সব সময় orElseGet।

  4. Use flatMap to get the first letter of a user's city (with nullable address).
    flatMap দিয়ে user-এর city-র প্রথম অক্ষর বের করুন (address null-হতে পারে)।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        record Addr(String city) {}
        record User(String name, Optional<Addr> addr) {}
    
        public static void main(String[] args) {
            User u = new User("Arif", Optional.of(new Addr("Dhaka")));
            char c = u.addr().map(Addr::city).map(s -> s.charAt(0)).orElse('?');
            System.out.println(c);
        }
    }
  5. Why should we not accept Optional<T> as a method parameter?
    Optional<T>-কে method parameter হিসেবে কেন গ্রহণ করা উচিত নয়?
    ✨ Show Answer

    Answer: Because the caller must build an Optional just to pass it — and then could still pass null, giving you three states to handle (null, empty, present) instead of two. Accept a plain value; overload the method, or use two methods, if some call sites have nothing to pass. The Java community's consensus: use Optional only as a return type.

    কারণ caller-কে Optional বানিয়ে pass করতে হবে — এবং তারপরও null-ও pass করতে পারে, ফলে তিনটি state সামলাতে হবে (null, empty, present)। প্লেইন value নিন; দরকার হলে method overload করুন। Java community-র convention: Optional-কে শুধু return type হিসেবে ব্যবহার করা।

Summary — Module 30

Optional<T> is Java's type-safe way to say "maybe a value." Use it primarily as a return type. Create with ofNullable, consume with ifPresent, orElseGet, or orElseThrow. Chain with map and flatMap to avoid nested null checks. Never use Optional as a method parameter, field, or collection element.

Optional<T> হলো type-safe "হয়তো একটি value"। মূলত return type হিসেবেই ব্যবহার করুন। ofNullable দিয়ে বানান, ifPresent/orElseGet/orElseThrow-দিয়ে consume করুন, map/flatMap-এ chain করুন। Parameter/field/collection-এ Optional রাখবেন না।

Next Module → Exception Handling ও try-with-resources।