Modern Java

Optional and Modern Null Handling

ReadingPreview

You are viewing a free preview lesson.

Lesson Overview

Java-তে null খুব পুরোনো এবং গুরুত্বপূর্ণ একটি concept।

কোনো reference:

Course course =
        null;

হতে পারে।

কিন্তু null-এর সবচেয়ে বড় সমস্যা হলো:

absence অনেক সময় implicit থাকে

Method signature দেখে বোঝা যায় না result:

always থাকবে
নাকি
missing হতে পারে

Example:

Course findCourse(
        String code
)

এই method কি:

Course return করবে?

null return করতে পারে?

exception throw করবে?

Signature দেখে clear নয়।

Modern Java-তে এই ধরনের optional result explicitভাবে represent করার জন্য আমরা ব্যবহার করতে পারি:

Optional<T>

Example:

Optional<Course> findCourse(
        String code
)

এখন method signature নিজেই বলে:

Course থাকতে পারে
অথবা
না-ও থাকতে পারে।

এই lesson-এ আমরা শিখব:

  • null কোথায় problem তৈরি করে
  • NullPointerException
  • Optional<T> কী
  • Optional.empty()
  • Optional.of()
  • Optional.ofNullable()
  • isPresent()
  • isEmpty()
  • ifPresent()
  • ifPresentOrElse()
  • map()
  • flatMap()
  • filter()
  • orElse()
  • orElseGet()
  • orElseThrow()
  • or()
  • Optional chaining
  • Optional return type কখন useful
  • Optional.get() কেন avoid করা ভালো
  • Optional parameter হিসেবে blindly ব্যবহার করা কেন উচিত নয়
  • Optional field হিসেবে overuse কেন problematic হতে পারে
  • Null validation এবং domain invariants
  • Practical null-handling strategies

The Problem with null

Suppose:

Course course =
        findCourse(
                "JAVA"
        );

Then:

System.out.println(
        course.title()
);

যদি findCourse() return করে:

null

তাহলে code throw করবে:

NullPointerException

NullPointerException

NullPointerException হয় যখন আমরা null reference-এর মাধ্যমে instance member access করার চেষ্টা করি।

Example:

String value =
        null;

System.out.println(
        value.length()
);

এখানে:

value

কোনো actual String object-কে point করছে না।

তাই:

value.length()

call করা সম্ভব নয়।


Traditional Null Check

একটি common approach:

Course course =
        findCourse(
                "JAVA"
        );

if (
        course != null
) {
    System.out.println(
            course.title()
    );
}

এটি perfectly valid।

Optional মানে এই নয় যে Java code-এ আর কখনো null check থাকবে না।

কিন্তু কিছু API boundary-তে Optional absence-কে clearerভাবে express করতে পারে।


What Is Optional<T>?

Optional<T> এমন একটি container-like type যা represent করে:

একটি non-null value আছে

অথবা:

কোনো value নেই

Example:

Optional<Course>

এর দুইটি possible state:

Optional containing Course

Optional.empty()

Important Mental Model

Optional<Course> মানে:

Course অথবা null

literally নয়।

Better mental model:

একটি result আছে কি না
সেটি explicitly represent করা হয়েছে।

Creating an Empty Optional

Optional<Course> course =
        Optional.empty();

Meaning:

Course পাওয়া যায়নি।

Optional.of()

Known non-null value থাকলে:

Course course =
        new Course(
                "JAVA",
                "Java Foundation"
        );

Optional<Course> result =
        Optional.of(
                course
        );

Important Rule for Optional.of()

এইটি safe only when value definitely non-null।

Optional.of(
        value
);

যদি:

value == null

হয়, তাহলে exception হবে।

So:

Optional.of(...)

means:

আমি জানি value non-null।

Optional.ofNullable()

Value null হতে পারে এমন situation-এ:

Optional<Course> result =
        Optional.ofNullable(
                course
        );

If:

course != null

then Optional contains Course।

If:

course == null

then:

Optional.empty()

of() vs ofNullable()

Use:

Optional.of(
        value
)

when:

null is not valid here

Use:

Optional.ofNullable(
        value
)

when:

source value may legitimately be null

Example Repository Lookup

Suppose repository internally uses a Map:

Map<String, Course> courses =
        new HashMap<>();

Map lookup:

courses.get(
        code
)

returns:

Course

or:

null

We can expose a clearer API:

Optional<Course> findByCode(
        String code
) {
    return Optional.ofNullable(
            courses.get(
                    code
            )
    );
}

Caller এখন signature দেখে জানে:

result missing হতে পারে।

isPresent()

Check:

Optional<Course> result =
        findByCode(
                "JAVA"
        );

if (
        result.isPresent()
) {
    ...
}

isPresent() returns:

true

when value exists।


Accessing the Value

Technically:

if (
        result.isPresent()
) {
    Course course =
            result.get();
}

works।

কিন্তু এই style অনেক সময় Optional-এর benefit কমিয়ে দেয়।

আমরা একটু পরে দেখব better APIs।


isEmpty()

Check absence:

if (
        result.isEmpty()
) {
    System.out.println(
            "Course not found."
    );
}

এটি অনেক সময়:

!result.isPresent()

এর চেয়ে readable।


Optional.get()

get() contained value return করে।

Course course =
        result.get();

কিন্তু Optional empty হলে:

NoSuchElementException

throw করবে।


Why get() Is Usually a Bad Default

এই code:

Course course =
        findByCode(
                code
        ).get();

আসলে missing case ignore করছে।

Optional-এর purpose ছিল:

absence explicit করা

কিন্তু get() দিয়ে blindly value বের করলে আমরা আবার unsafe assumption করছি।

Better APIs:

orElse(...)
orElseGet(...)
orElseThrow(...)
ifPresent(...)
map(...)

ifPresent()

যদি value থাকে তখন একটি action run করতে চাই:

findByCode(
        "JAVA"
).ifPresent(
        course ->
                System.out.println(
                        course.title()
                )
);

If Course missing:

nothing happens

Method Reference

Optional<String> title =
        Optional.of(
                "Java"
        );

title.ifPresent(
        System.out::println
);

When ifPresent() Is Useful

Use when requirement is:

value থাকলে এই কাজ করো
absence হলে কিছু করার দরকার নেই

Example:

optional audit value print করা
optional cache entry process করা
optional lookup result display করা

ifPresentOrElse()

যদি presence এবং absence দুইটির জন্য আলাদা behavior দরকার হয়:

result.ifPresentOrElse(
        course ->
                System.out.println(
                        course.title()
                ),
        () ->
                System.out.println(
                        "Course not found."
                )
);

First argument:

value থাকলে কী হবে

Second:

value না থাকলে কী হবে

Optional.map()

Optional.map() contained value থাকলে transform করে।

Suppose:

Optional<Course> course =
        findByCode(
                "JAVA"
        );

Course থেকে title চাই।

Optional<String> title =
        course.map(
                Course::title
        );

What Happens If Optional Is Empty?

If:

course

is:

Optional.empty()

then:

course.map(
        Course::title
)

returns:

Optional.empty()

Mapping function run করে না।


Optional map() Mental Model

Value আছে?
↓ yes
transform করো

Value নেই?
↓
empty-ই থাকো

Avoid Manual Presence Checks

Instead of:

Optional<Course> result =
        findByCode(
                code
        );

if (
        result.isPresent()
) {
    Course course =
            result.get();

    String title =
            course.title();

    ...
}

we can write:

Optional<String> title =
        findByCode(
                code
        ).map(
                Course::title
        );

আরও directly transformation express করছে।


Chaining map()

Suppose:

record Course(
        String code,
        Instructor instructor
) {
}

record Instructor(
        String name
) {
}

If Course exists:

Optional<String> instructorName =
        findByCode(
                "JAVA"
        )
                .map(
                        Course::instructor
                )
                .map(
                        Instructor::name
                );

Chaining Means Absence Propagates

If Course missing:

empty

then next mappings run করবে না।

If Course exists, instructor transform হবে।

This reduces repeated:

if (
        value != null
)

chains।


Null Returned from map()

If mapping function returns null, Optional.map() result becomes empty।

Still, ideally mapping functions should have clear null contracts।

Better domain design often avoids nullable intermediate values where absence has explicit meaning।


Optional.filter()

Optional-এর contained value condition satisfy করলে রাখে।

Example:

Optional<Course> publishedCourse =
        findByCode(
                "JAVA"
        ).filter(
                Course::published
        );

Behavior

If Course:

exists
AND
published

then result contains Course।

Otherwise:

Optional.empty()

Another Example

Optional<Course> premium =
        findByCode(
                code
        ).filter(
                course ->
                        course.priceInPaisa()
                        >= 500_000
        );

filter() Mental Model

Optional value আছে?
↓
condition true?
↓
yes → keep

no → empty

orElse()

Value না থাকলে default value দিতে:

String title =
        findByCode(
                code
        )
                .map(
                        Course::title
                )
                .orElse(
                        "Unknown Course"
                );

If found:

actual title

If missing:

Unknown Course

Simple orElse() Example

Optional<String> value =
        Optional.empty();

String result =
        value.orElse(
                "Default"
        );

Result:

Default

orElseGet()

Fallback value যদি lazily create করতে চাই:

String title =
        optionalTitle.orElseGet(
                () ->
                        buildFallbackTitle()
        );

Fallback supplier only needed হলে execute হবে।


orElse() vs orElseGet()

এটি important।

With:

optional.orElse(
        createFallback()
);

createFallback() argument evaluate হবে method call-এর আগে।

অর্থাৎ Optional value present হলেও expensive fallback তৈরি হতে পারে।


orElseGet() Is Lazy

optional.orElseGet(
        () ->
                createFallback()
);

এখানে createFallback() only run করবে যখন Optional empty।


Example

Suppose:

String fallback =
        loadFromRemoteService();

expensive।

Avoid:

optional.orElse(
        loadFromRemoteService()
);

when unnecessary execution matters।

Prefer:

optional.orElseGet(
        () ->
                loadFromRemoteService()
);

When orElse() Is Fine

Simple constant fallback:

.orElse(
        "Unknown"
)

No issue।

For cheap existing object:

.orElse(
        defaultCourse
)

also fine।


orElseThrow()

যদি absence exceptional হয়:

Course course =
        findByCode(
                code
        ).orElseThrow(
                () ->
                        new IllegalArgumentException(
                                "Course not found: "
                                + code
                        )
        );

এটি খুব common pattern।


Mental Model

Course আছে?
→ return Course

Course নেই?
→ throw exception

Why orElseThrow() Is Better Than get()

Compare:

findByCode(
        code
).get();

Failure:

NoSuchElementException

with little domain meaning।

Better:

findByCode(
        code
).orElseThrow(
        () ->
                new CourseNotFoundException(
                        code
                )
);

Now failure expresses:

actual domain problem

Custom Exception Example

final class CourseNotFoundException
        extends RuntimeException {

    CourseNotFoundException(
            String code
    ) {
        super(
                "Course not found: "
                + code
        );
    }
}

Then:

Course course =
        repository.findByCode(
                code
        ).orElseThrow(
                () ->
                        new CourseNotFoundException(
                                code
                        )
        );

No-Argument orElseThrow()

Optional also supports:

optional.orElseThrow();

If empty, it throws:

NoSuchElementException

Usually domain code-এ meaningful exception supplier clearer হতে পারে।


or()

Suppose first lookup fails হলে second Optional-producing source try করতে চাই।

Example:

Optional<Course> course =
        primaryRepository.findByCode(
                code
        ).or(
                () ->
                        backupRepository.findByCode(
                                code
                        )
        );

Why or() Is Different from orElseGet()

orElseGet() gives:

T

fallback।

or() gives:

Optional<T>

fallback।

So Optional-producing fallback chain-এর জন্য or() useful।


Optional Chaining Example

Suppose:

Optional<Course> course =
        findByCode(
                code
        );

Need published Course title or fallback:

String title =
        course
                .filter(
                        Course::published
                )
                .map(
                        Course::title
                )
                .orElse(
                        "Unavailable"
                );

Read:

Course থাকলে
↓
published হলে রাখো
↓
title বের করো
↓
না থাকলে "Unavailable"

This Is Where Optional Becomes Useful

Without Optional:

Course course =
        findByCodeNullable(
                code
        );

String title;

if (
        course == null
        || !course.published()
) {
    title =
            "Unavailable";
} else {
    title =
            course.title();
}

Optional version:

String title =
        findByCode(
                code
        )
                .filter(
                        Course::published
                )
                .map(
                        Course::title
                )
                .orElse(
                        "Unavailable"
                );

দুইটিই valid।

কিন্তু second version absence/transformation flow compactভাবে express করছে।


flatMap() with Optional

Stream-এর মতো Optional-এও flatMap() আছে।

এটি useful যখন mapping function নিজেই:

Optional<R>

return করে।


Example

Suppose:

Optional<Course> findCourse(
        String code
)

and:

Optional<Instructor> findInstructor(
        Course course
)

If we do:

course.map(
        this::findInstructor
)

result becomes:

Optional<Optional<Instructor>>

Nested Optional।


Use flatMap()

Optional<Instructor> instructor =
        course.flatMap(
                this::findInstructor
        );

Now nested Optional flatten হয়ে:

Optional<Instructor>

হয়।


map() vs flatMap() for Optional

Same core idea as Stream।

If mapper returns normal value:

Course
→ String

use:

map()

If mapper returns Optional:

Course
→ Optional<Instructor>

use:

flatMap()

Example Chain

Optional<String> instructorName =
        findCourse(
                code
        )
                .flatMap(
                        this::findInstructor
                )
                .map(
                        Instructor::name
                );

Flow:

Course exists?
↓
Instructor exists?
↓
name

যেকোনো stage missing হলে result:

Optional.empty()

Avoid Optional<Optional<T>>

যদি code-এ দেখেন:

Optional<Optional<Course>>

অনেক ক্ষেত্রে সেটি signal হতে পারে:

flatMap() দরকার

অথবা API abstraction rethink করা দরকার।


Optional Return Types

Optional<T> সবচেয়ে natural যখন method:

একটি result search করছে
এবং absence normal possibility

Example:

Optional<Course> findByCode(
        String code
);

Good Optional Return Examples

Optional<Learner> findByEmail(
        String email
);

Optional<Course> findByCode(
        String code
);

Optional<Enrollment> findActiveEnrollment(
        long learnerId
);

যদি "not found" normal result হয়।


When Optional May Not Be Needed

Suppose method contract guarantees Course must exist:

Course getRequiredCourse(
        String code
)

and absence means programming/domain error।

Then method itself exception throw করতে পারে।

Example:

Course getRequiredCourse(
        String code
) {
    return repository.findByCode(
            code
    ).orElseThrow(
            () ->
                    new CourseNotFoundException(
                            code
                    )
    );
}

Caller gets:

Course

because this application-service boundary guarantees either:

Course

or:

exception

find vs getRequired

Naming can communicate semantics।

Example:

Optional<Course> findByCode(
        String code
)

suggests:

absence normal

While:

Course requireByCode(
        String code
)

suggests:

must exist

Clear APIs reduce ambiguity।


Optional Is Not a Replacement for Validation

Suppose:

Course(
        null,
        "Java"
)

is invalid।

Do not solve invariant problem by making every field:

Optional<String>

Instead constructor validation:

this.code =
        Objects.requireNonNull(
                code
        );

may be correct।


Required Data Should Usually Be Required

If Course must always have title:

record Course(
        String code,
        String title
) {
    Course {
        Objects.requireNonNull(
                code
        );

        Objects.requireNonNull(
                title
        );
    }
}

Do not make:

Optional<String> title

just because null exists in Java।


Optional Means Legitimate Absence

Good concept:

middleName may not exist

Potentially optional।

But:

Course code

if domain requires it:

not optional

The question is not:

Can Java represent null?

The question is:

Does absence make sense in the domain?

Optional as a Method Parameter

You can technically write:

void createCourse(
        Optional<String> description
)

কিন্তু this is often awkward।

Caller must write:

createCourse(
        Optional.of(
                description
        )
);

or:

createCourse(
        Optional.empty()
);

Better Parameter Design

Depending on context, prefer:

createCourse(
        String description
)

with explicit nullable contract if necessary,

or overloads,

or a request object with clear semantics।

Optional is most idiomatic as a:

return type

for potentially absent result।


Optional Fields

Technically possible:

class Course {

    private Optional<String> description;
}

কিন্তু domain model-এর every optional field-কে Optional বানানো usually unnecessary।

It can introduce:

extra wrapping
serialization complexity
ORM/framework friction
awkward APIs

depending on environment।


Better Domain Modeling

If field is legitimately optional, you need an explicit project policy।

Possible strategies:

nullable internal field with strict boundary handling
dedicated value object
separate type/state
Optional-returning accessor

There is no universal rule that every nullable field should be stored as Optional


Avoid Returning null Optional

Never write:

Optional<Course> findByCode(
        String code
) {
    return null;
}

This defeats the entire purpose।

An Optional-returning method should return:

Optional.of(...)

or:

Optional.empty()

but not:

null

Optional Itself Should Not Be Null

Caller should be able to assume:

Optional<Course> result =
        findByCode(
                code
        );

result itself exists।

Its contents may be absent।


Do Not Wrap Collections in Optional Without Good Reason

Suppose:

List<Course> findPublishedCourses()

If there are no Courses, return:

List.of()

rather than:

Optional<List<Course>>

in most cases।


Why Empty Collection Is Better

A collection already naturally represents:

zero items

So:

List<Course>

can represent:

0
1
many

No need for another absence layer।


Prefer

List<Course> findAll()

returns:

[]

when no data।

Instead of:

Optional<List<Course>>

unless absence and empty collection truly mean different domain states।


Optional of Boolean Is Often Suspicious

Optional<Boolean>

can represent:

true
false
missing

Sometimes this genuinely matters।

But often an enum is clearer।

Example:

VerificationStatus {
    VERIFIED,
    NOT_VERIFIED,
    UNKNOWN
}

can communicate semantics better than:

Optional<Boolean>

Optional Should Clarify, Not Hide

Good Optional usage:

Optional<Course> findByCode(...)

Clear:

maybe found

Poor design:

Optional<Optional<Boolean>>

Now semantics become hard to interpret।


Optional and Exceptions

Optional absence এবং exception একই concept নয়।

Optional:

No matching value

Exception:

Operation failed

Example:

Course not found

might be normal lookup absence।

But:

repository storage corrupted

is failure।

Do not convert every exception into:

Optional.empty()

Bad Example

try {
    return Optional.of(
            loadCourse()
    );
} catch (
        Exception exception
) {
    return Optional.empty();
}

এটি dangerous।

Now caller cannot distinguish:

Course missing

from:

Disk failed
Parsing failed
Permission denied
Program bug

Preserve Failure Semantics

Optional is for:

absence

not:

swallow every error

Real failure should still be represented appropriately:

exception
error result
failure abstraction

depending on design।


Null at External Boundaries

Sometimes external systems return nullable values।

Examples:

legacy API
database driver
third-party library
old Java API

At boundary, we can normalize:

Optional.ofNullable(
        externalValue
)

Then internal code can use explicit absence handling।


Example

String rawTitle =
        legacyClient.findTitle(
                code
        );

Optional<String> title =
        Optional.ofNullable(
                rawTitle
        );

But Do Not Wrap Everything Automatically

If external null means:

contract violation

then:

Objects.requireNonNull(...)

may be better।

Again:

What does absence mean?

comes first।


Objects.requireNonNull()

For required values:

this.code =
        Objects.requireNonNull(
                code,
                "code must not be null"
        );

This establishes invariant early।


Fail Fast

If Course code is required, fail at construction:

new Course(
        null,
        ...
)

rather than allowing invalid object and failing much later।

This is called:

fail fast

Optional and Domain Invariants

Consider two different cases:

Case 1

Course title

must always exist।

Use:

required non-null field

Case 2

Search:

find Course by code

may find nothing।

Use:

Optional<Course>

These are different types of absence।


Optional Pipeline Example

Suppose:

Optional<Course> findByCode(
        String code
)

and Course:

record Course(
        String code,
        String title,
        long priceInPaisa,
        boolean published
) {
}

Need:

published Course হলে title
otherwise "Unavailable"
String title =
        findByCode(
                code
        )
                .filter(
                        Course::published
                )
                .map(
                        Course::title
                )
                .orElse(
                        "Unavailable"
                );

Another Pipeline Example

Need:

Course price
না থাকলে 0
long price =
        findByCode(
                code
        )
                .map(
                        Course::priceInPaisa
                )
                .orElse(
                        0L
                );

But ask:

Does 0 actually mean missing?

If free Course can legitimately have:

price = 0

then this fallback loses information।

Maybe keeping:

Optional<Long>

is better।


Default Values Can Hide Meaning

This:

.orElse(
        0L
)

may collapse:

Course missing

and:

Course exists and is free

into same result।

So default value selection must preserve semantics।


Optional Can Protect Meaning

Sometimes caller should explicitly deal with absence:

Optional<Long> price =
        findByCode(
                code
        ).map(
                Course::priceInPaisa
        );

rather than automatically defaulting।


orElseThrow() at Application Boundary

Repository:

Optional<Course> findByCode(
        String code
);

Service:

Course findRequired(
        String code
) {
    return repository.findByCode(
            code
    ).orElseThrow(
            () ->
                    new CourseNotFoundException(
                            code
                    )
    );
}

এটি clean layering pattern হতে পারে।

Repository says:

maybe exists

Application service decides:

এই use case-এ must exist

Complete Example

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

public class Main {

    public static void main(String[] args) {
        CourseRepository repository =
                new CourseRepository();

        repository.save(
                new Course(
                        "JAVA",
                        "Java Foundation",
                        true
                )
        );

        String title =
                repository.findByCode(
                        "JAVA"
                )
                        .filter(
                                Course::published
                        )
                        .map(
                                Course::title
                        )
                        .orElse(
                                "Unavailable"
                        );

        System.out.println(
                title
        );

        String missing =
                repository.findByCode(
                        "BACKEND"
                )
                        .map(
                                Course::title
                        )
                        .orElse(
                                "Not found"
                        );

        System.out.println(
                missing
        );
    }

    record Course(
            String code,
            String title,
            boolean published
    ) {
    }

    static final class CourseRepository {

        private final Map<String, Course> courses =
                new HashMap<>();

        void save(
                Course course
        ) {
            courses.put(
                    course.code(),
                    course
            );
        }

        Optional<Course> findByCode(
                String code
        ) {
            return Optional.ofNullable(
                    courses.get(
                            code
                    )
            );
        }
    }
}

Output:

Java Foundation
Not found

Practical Example — Required Course

static Course requireCourse(
        CourseRepository repository,
        String code
) {
    return repository.findByCode(
            code
    ).orElseThrow(
            () ->
                    new IllegalArgumentException(
                            "Course not found: "
                            + code
                    )
    );
}

Practical Example — Fallback Lookup

Suppose current code না পেলে legacy code lookup করতে চাই।

Optional<Course> course =
        repository.findByCode(
                currentCode
        ).or(
                () ->
                        repository.findByCode(
                                legacyCode
                        )
        );

Practical Example — Nested Optional

Suppose:

Optional<Course> findCourse(
        String code
)

and:

Optional<Instructor> findInstructor(
        Course course
)

Use:

Optional<String> instructorName =
        findCourse(
                code
        )
                .flatMap(
                        this::findInstructor
                )
                .map(
                        Instructor::name
                );

Common Mistake 1 — Calling get() Immediately

Bad default:

repository.findByCode(
        code
).get();

This ignores absence।

Prefer an API that expresses intended behavior:

orElse(...)
orElseGet(...)
orElseThrow(...)
map(...)
ifPresent(...)

Common Mistake 2 — isPresent() + get() Everywhere

Example:

if (
        optional.isPresent()
) {
    Course course =
            optional.get();
}

Not always wrong।

কিন্তু যদি every Optional use এভাবেই লেখা হয়, তাহলে আপনি essentially nullable-style branching-ই লিখছেন।

Check whether:

map()
filter()
orElseThrow()
ifPresent()

intent better express করে কি না।


Common Mistake 3 — orElse() with Expensive Work

Avoid:

optional.orElse(
        expensiveFallback()
);

if fallback expensive and usually unnecessary।

Use:

optional.orElseGet(
        () ->
                expensiveFallback()
);

Common Mistake 4 — Returning null from Optional Method

Never:

Optional<Course> findByCode(...) {
    return null;
}

Return:

Optional.empty();

Common Mistake 5 — Optional Everywhere

Do not automatically write:

Optional<String> code
Optional<String> title
Optional<Long> price
Optional<Boolean> published

for every field।

Required data should remain required।


Common Mistake 6 — Optional Collection

Avoid:

Optional<List<Course>>

when:

List<Course>

with empty List naturally represents no results।


Common Mistake 7 — Hiding Exceptions as Empty

Do not catch every failure and return:

Optional.empty()

Absence এবং operational failure are different।


Common Mistake 8 — Meaningless Defaults

price.orElse(
        0L
)

may hide difference between:

missing

and:

free

Choose fallback values carefully।


Common Mistake 9 — Nested Optional

If result becomes:

Optional<Optional<T>>

check whether:

flatMap()

is appropriate।


Common Mistake 10 — Using Optional Instead of a Domain State

Sometimes three or more meaningful states exist।

Instead of:

Optional<Boolean>

a domain enum may be clearer:

enum ReviewStatus {
    APPROVED,
    REJECTED,
    NOT_REVIEWED
}

Practice 1 — Create Empty Optional

Solution

Optional<String> value =
        Optional.empty();

Practice 2 — Known Non-Null Value

Solution

Optional<String> value =
        Optional.of(
                "Java"
        );

Practice 3 — Nullable Value

Solution

Optional<String> value =
        Optional.ofNullable(
                input
        );

Practice 4 — Transform Value

Given:

Optional<Course> course

get optional title।

Solution

Optional<String> title =
        course.map(
                Course::title
        );

Practice 5 — Keep Only Published Course

Solution

Optional<Course> published =
        course.filter(
                Course::published
        );

Practice 6 — Default String

Return:

Unknown

when title missing।

Solution

String title =
        optionalTitle.orElse(
                "Unknown"
        );

Practice 7 — Lazy Fallback

Fallback is expensive।

Solution

String value =
        optional.orElseGet(
                () ->
                        buildFallback()
        );

Practice 8 — Throw When Missing

Solution

Course course =
        optional.orElseThrow(
                () ->
                        new IllegalArgumentException(
                                "Course missing."
                        )
        );

Practice 9 — map() or flatMap()

Given:

Course -> String

Use?

Answer

map()

Practice 10

Given:

Course -> Optional<Instructor>

Use?

Answer

flatMap()

Practice 11 — Empty Collection

Method may find zero Courses।

Return:

Optional<List<Course>>

or:

List<Course>

Answer

Usually:

List<Course>

and return empty List when none exist।


Practice 12 — Required Field

Every Course must have a code।

Should field type be:

Optional<String>

Answer

Usually no।

Make code required and validate:

Objects.requireNonNull(...)

plus other domain validation।


Practice 13 — orElse() vs orElseGet()

Which one lazily executes fallback logic?

Answer

orElseGet()

Practice 14 — Optional Lookup

Why is this signature useful?

Optional<Course> findByCode(
        String code
);

Answer

Because method contract explicitly says:

matching Course may not exist

without relying on undocumented null behavior।


Practice 15 — Error Handling

Database connection fails while looking for a Course।

Should repository silently return:

Optional.empty()

Answer

Usually no।

That is an operational failure, not simply:

Course not found

Failure semantics should be preserved।


True or False

  1. Optional<T> can represent a value or absence.
  2. Optional.of(null) produces Optional.empty().
  3. Optional.ofNullable(null) produces empty Optional.
  4. isEmpty() checks whether value is absent.
  5. get() is always the preferred Optional API.
  6. map() transforms a present value.
  7. filter() can turn a present Optional into empty.
  8. orElse() provides a fallback value.
  9. orElseGet() can create fallback lazily.
  10. orElseThrow() can convert absence into a meaningful exception.
  11. flatMap() is useful when mapping function returns Optional.
  12. Every Java field should use Optional to avoid null.
  13. Empty List usually makes Optional<List<T>> unnecessary.
  14. Optional-returning methods should themselves return null when absent.
  15. Optional absence and operational failure mean the same thing.

Answers

1. True
2. False
3. True
4. True
5. False
6. True
7. True
8. True
9. True
10. True
11. True
12. False
13. True
14. False
15. False

Knowledge Check

Question 1

Optional<T> কী represent করে?

Question 2

Optional.of() এবং Optional.ofNullable()-এর difference কী?

Question 3

কেন Optional.get() blindly ব্যবহার করা উচিত নয়?

Question 4

Optional.map() কী করে?

Question 5

Optional.filter() কী করে?

Question 6

orElse() এবং orElseGet()-এর গুরুত্বপূর্ণ difference কী?

Question 7

orElseThrow() কখন useful?

Question 8

map() এবং flatMap()-এর difference কী?

Question 9

কেন Optional<List<T>> অনেক সময় unnecessary?

Question 10

কেন Optional required domain fields-এর replacement নয়?

Question 11

Optional absence এবং exception failure-এর difference কী?

Question 12

কোন ধরনের method return type হিসেবে Optional সবচেয়ে natural?


Knowledge Check Answers

Answer 1

Optional<T> explicitly represent করে যে একটি non-null value থাকতে পারে অথবা কোনো value নাও থাকতে পারে।

Answer 2

Optional.of(value) requires value non-null।

Optional.ofNullable(value) null হলে empty Optional তৈরি করে।

Answer 3

Optional empty হলে get() exception throw করে এবং absence handling intention clear করে না।

orElse(), orElseThrow(), map(), ifPresent()-এর মতো APIs সাধারণত intent better express করে।

Answer 4

Value present থাকলে mapping function apply করে এবং transformed value-কে Optional-এর মধ্যে রাখে।

Empty হলে empty-ই থাকে।

Answer 5

Present value condition satisfy করলে রাখে, otherwise empty Optional return করে।

Answer 6

orElse()-এর fallback expression eagerly evaluate হতে পারে।

orElseGet() Supplier ব্যবহার করে fallback only when Optional empty তখন তৈরি করে।

Answer 7

যখন current use case-এ value missing থাকা acceptable নয় এবং absence-কে meaningful exception-এ convert করতে চাই।

Answer 8

Mapper normal value return করলে:

map()

Mapper যদি:

Optional<R>

return করে, nested Optional avoid করতে:

flatMap()

use করা হয়।

Answer 9

List নিজেই zero elements represent করতে পারে।

No result-এর জন্য:

List.of()

সাধারণত sufficient।

Answer 10

যদি domain field অবশ্যই থাকতে হয়, correct solution হলো invariant enforce করা, যেমন:

Objects.requireNonNull(...)

Optional legitimate absence represent করে, invalid missing required data নয়।

Answer 11

Optional empty সাধারণত valid absence represent করে।

Exception operation failure, invalid state বা unexpected problem represent করতে পারে।

একটিকে অন্যটির মধ্যে silently convert করলে important information হারাতে পারে।

Answer 12

যে lookup/query method একটি single result খোঁজে এবং result না পাওয়া normal possibility।

Example:

Optional<Course> findByCode(
        String code
);

Optional Decision Guide

Single lookup, may not exist:

Optional<Course>

Many results, may be zero:

List<Course>

Required value:

Course

plus validation বা exception।

Value exists but fallback needed:

orElse(...)

Expensive fallback:

orElseGet(...)

Missing should fail:

orElseThrow(...)

Transform present value:

map(...)

Transform using Optional-returning function:

flatMap(...)

Keep only if condition matches:

filter(...)

Optional Cheat Sheet

Empty

Optional.empty()

Known Non-Null

Optional.of(
        value
)

Nullable Input

Optional.ofNullable(
        value
)

Check Presence

optional.isPresent()

Check Absence

optional.isEmpty()

Run If Present

optional.ifPresent(
        value ->
                process(
                        value
                )
);

Transform

optional.map(
        value ->
                transform(
                        value
                )
);

Optional-Producing Transformation

optional.flatMap(
        value ->
                findSomething(
                        value
                )
);

Filter

optional.filter(
        value ->
                condition
);

Default

optional.orElse(
        fallback
);

Lazy Default

optional.orElseGet(
        () ->
                createFallback()
);

Fail if Missing

optional.orElseThrow(
        () ->
                new IllegalStateException(
                        "Required value missing."
                )
);

Core Mental Model

Optional বুঝতে সবচেয়ে useful question:

Absence কি এই operation-এর একটি valid result?

If yes:

Optional<T>

useful হতে পারে।

Example:

Find Course by code
→ Course থাকতে পারে
→ না-ও থাকতে পারে

But:

Create a valid Course

এখানে required code missing হওয়া legitimate state নয়।

তাই:

Optional code

না বানিয়ে invariant enforce করুন।

আর Optional chain পড়ুন এভাবে:

findByCode(
        code
)
        .filter(
                Course::published
        )
        .map(
                Course::title
        )
        .orElse(
                "Unavailable"
        );

Meaning:

Course খুঁজো
↓
পেলে published কি না দেখো
↓
published হলে title নাও
↓
কিছু না থাকলে fallback দাও

Lesson Summary

এই lesson-এ আমরা Java-তে modern null handling এবং Optional<T>-এর foundation শিখেছি।

আমরা শিখেছি:

  • null missing value represent করতে পারে, কিন্তু API contract ambiguous করতে পারে
  • Optional<T> presence বা absence explicitly represent করে
  • Optional.empty() absence represent করে
  • Optional.of() known non-null value-এর জন্য
  • Optional.ofNullable() nullable source value handle করতে পারে
  • isPresent() এবং isEmpty() state check করে
  • get() blindly ব্যবহার করা unsafe
  • ifPresent() value থাকলে action চালায়
  • ifPresentOrElse() presence এবং absence দুইটি branch handle করতে পারে
  • map() contained value transform করে
  • filter() condition না মিললে Optional empty করতে পারে
  • flatMap() Optional-returning transformation flatten করে
  • orElse() fallback value দেয়
  • orElseGet() lazy fallback দেয়
  • orElseThrow() absence-কে meaningful failure-এ convert করতে পারে
  • or() alternative Optional source try করতে পারে
  • Optional return type single-result lookup-এর জন্য natural
  • Optional required domain fields-এর replacement নয়
  • Optional parameter বা field হিসেবে blindly ব্যবহার করা উচিত নয়
  • Empty collection সাধারণত Optional<List<T>>-এর চেয়ে cleaner
  • Optional-returning method কখনো null return করা উচিত নয়
  • Absence এবং operational failure আলাদা concepts
  • Domain invariants যত early সম্ভব enforce করা ভালো
  • Default value ব্যবহার করার আগে semantic information হারাচ্ছে কি না ভাবতে হবে

সবচেয়ে গুরুত্বপূর্ণ principle:

Optional-এর কাজ null লুকানো নয়।

Optional-এর কাজ meaningful absence
explicit করা।

আর practical rule:

May exist
→ Optional

Must exist
→ required value

Many results
→ collection, possibly empty

Operation failed
→ proper failure handling

Next Lesson

পরবর্তী lesson:

The Modern Date and Time API

আমরা শিখব:

  • কেন old date/time APIs problematic ছিল
  • LocalDate
  • LocalTime
  • LocalDateTime
  • Instant
  • Duration
  • Period
  • ZoneId
  • ZonedDateTime
  • DateTimeFormatter
  • Date arithmetic
  • Time-zone conversion
  • LocalDateTime কেন absolute moment নয়
  • Instant কখন ব্যবহার করা উচিত
  • User-facing local time এবং system timestamps
  • Common date/time mistakes