Enums, Exceptions, and Robust Error Handling

Checked vs Unchecked Exceptions

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Lesson Overview

Java exceptions দুইটি broad group-এ ভাগ করা হয়:

Checked exceptions
Unchecked exceptions

Difference শুধু inheritance নয়।

Difference-এর একটি গুরুত্বপূর্ণ অংশ হলো:

Compiler callerকে কী করতে বাধ্য করে?

Checked exception হলে callerকে:

catch

অথবা:

throws

করতে হয়।

Unchecked exception হলে compiler এই acknowledgement বাধ্যতামূলক করে না।

কিন্তু একটি common ভুল ধারণা হলো:

Checked exception = Recoverable
Unchecked exception = Unrecoverable

এটি সবসময় true নয়।

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

  • Checked exception কী
  • Unchecked exception কী
  • Compiler enforcement
  • RuntimeException
  • Recoverability myth
  • Validation এবং programming failures
  • Infrastructure failures
  • API design trade-offs
  • Exception declaration pollution
  • Checked exception wrapping
  • Custom exception checked না unchecked হওয়া উচিত কীভাবে decide করতে হয়
  • Layer boundary-তে exception translation

Learning Objectives

এই lesson শেষে আপনি পারবেন:

  • Checked এবং unchecked exception identify করতে
  • Compiler behavior explain করতে
  • RuntimeException hierarchy বুঝতে
  • Recoverability এবং exception category আলাদা করতে
  • Checked exception API-এর cost বুঝতে
  • Unchecked exception misuse identify করতে
  • Infrastructure failure translate করতে
  • Custom exception checked না unchecked হবে decide করতে
  • Layer অনুযায়ী exception contract design করতে

The Core Difference

Checked Exception

A checked exception:

  • Exception extend করে
  • কিন্তু RuntimeException extend করে না
  • Callerকে catch বা declare করতে হয়

Example:

IOException

Unchecked Exception

An unchecked exception:

  • RuntimeException extend করে
  • Compiler callerকে catch বা declare করতে বাধ্য করে না

Examples:

IllegalArgumentException
IllegalStateException
NullPointerException
NumberFormatException

Checked Exception Example

public static String readFile(
        Path path
) throws IOException {
    return Files.readString(
            path
    );
}

Caller:

String content =
        readFile(
                path
        );

This does not compile unless caller:

try {
    String content =
            readFile(
                    path
            );
} catch (
        IOException exception
) {
    // Handle
}

or declares:

public static void load()
        throws IOException {
    String content =
            readFile(
                    path
            );
}

Unchecked Exception Example

public static void validatePrice(
        long priceInPaisa
) {
    if (priceInPaisa < 0) {
        throw new IllegalArgumentException(
                "Price cannot be negative."
        );
    }
}

Caller:

validatePrice(
        -100L
);

Compiler does not require:

try-catch

or:

throws

The exception can still occur at runtime।


Compiler Enforcement

Checked exceptions create an explicit compile-time contract।

public String loadContent()
        throws IOException

Caller cannot ignore the declared checked failure।

Unchecked exceptions remain part of the method contract conceptually, but compiler does not enforce acknowledgement।

Example:

public void publish()

may throw:

IllegalStateException

even though signature does not show it।


Does throws Mean the Method Will Fail?

No।

public String readFile(
        Path path
) throws IOException

means:

The method may complete normally
or
The method may propagate IOException

throws declares possibility, not certainty।


The Recoverability Myth

A common rule taught too simply:

Checked exceptions are recoverable.
Unchecked exceptions are programming errors.

This is only partially useful।

Consider:

IOException

Can caller always recover?

Not necessarily।

If required course content cannot be read, the application may have no valid fallback।

Consider:

NumberFormatException

Can caller recover?

Yes, an input boundary can ask the user to enter a valid number।

So recoverability alone does not define checked vs unchecked।


Better Decision Factors

When choosing checked or unchecked, ask:

  1. Can callers meaningfully act on the failure?
  2. Should every caller be forced to acknowledge it?
  3. Is the failure part of the abstraction’s normal contract?
  4. Will the exception cross many layers?
  5. Would catch-or-declare create noise without useful recovery?
  6. Is the failure caused by invalid usage or broken state?
  7. Is the lower-level implementation detail appropriate to expose?

Common Unchecked Validation Exceptions

IllegalArgumentException

Use when caller passes an invalid argument।

public Course(
        long id,
        String title
) {
    if (id <= 0) {
        throw new IllegalArgumentException(
                "Course ID must be positive."
        );
    }

    if (
            title == null
            || title.isBlank()
    ) {
        throw new IllegalArgumentException(
                "Course title is required."
        );
    }
}

IllegalStateException

Use when current state does not allow the operation।

public void publish() {
    if (
            status
            != CourseStatus.REVIEW
    ) {
        throw new IllegalStateException(
                "Only a reviewed course can be published."
        );
    }
}

Why Validation Exceptions Are Usually Unchecked

If a method requires:

Positive ID
Non-blank title
Valid state

these are programming contracts।

Forcing every caller to write:

try {
} catch (
        IllegalArgumentException exception
) {
}

would add noise।

Callers should usually prevent invalid calls rather than recover from them everywhere।


Unchecked Does Not Mean Optional Documentation

Compiler does not force acknowledgement, but API should still communicate important unchecked failures।

/**
 * Publishes a reviewed course.
 *
 * @throws IllegalStateException
 *         if the course is not in review.
 */
public void publish() {
}

Unchecked exception contracts still deserve clear naming, validation, and documentation।


Common Checked Exceptions

Examples:

IOException
SQLException
ClassNotFoundException
InterruptedException

These often originate from operations involving:

  • Files
  • Network
  • Database
  • Thread coordination
  • External resources

But library and framework design varies।

For example, some database frameworks wrap SQLException into unchecked exceptions।


Why Checked Exceptions Can Be Useful

Checked exceptions can be valuable when:

  • Failure is central to the operation
  • Caller has meaningful alternatives
  • Caller must make a deliberate choice
  • Ignoring the failure would be dangerous
  • The API is low-level and close to the resource

Example:

public String readFile(
        Path path
) throws IOException

Caller may:

  • Retry
  • Use another file
  • Report missing file
  • Stop the import
  • Provide a valid fallback

Why Checked Exceptions Can Become Burdensome

Suppose a repository method declares:

Course findByCode(
        CourseCode code
) throws SQLException;

Service method must declare:

Course loadCourse(
        CourseCode code
) throws SQLException;

Then API handler:

Response handle(
        CourseCode code
) throws SQLException;

Now low-level database detail leaks through every layer।

This is exception declaration pollution।


Exception Declaration Pollution

A checked exception can spread through methods that cannot meaningfully handle it।

Repository
↓ throws SQLException
Service
↓ throws SQLException
Handler
↓ throws SQLException
Controller

Problems:

  • Higher layers depend on lower-level technology
  • Method signatures become noisy
  • Callers may add meaningless catches
  • Refactoring storage technology becomes harder

Translate at the Appropriate Boundary

Repository implementation can catch:

SQLException

and translate it to an application-level exception।

public Course findByCode(
        CourseCode code
) {
    try {
        return executeQuery(
                code
        );
    } catch (
        SQLException exception
    ) {
        throw new CourseRepositoryException(
                "Could not load course "
                + code
                + ".",
                exception
        );
    }
}

CourseRepositoryException may be unchecked।

Now service does not depend directly on JDBC-specific SQLException


Why Translation Helps

Before:

throws SQLException

Higher layers know database implementation details।

After:

CourseRepositoryException

Higher layers know only:

Course persistence failed

The cause chain still preserves:

SQLException

for debugging।


Checked Infrastructure Exceptions

Low-level Java APIs often expose checked exceptions।

Example:

Files.readString(
        path
)

throws:

IOException

An application adapter may translate it:

public String loadCourseContent(
        Path path
) {
    try {
        return Files.readString(
                path
        );
    } catch (
        IOException exception
    ) {
        throw new CourseContentLoadException(
                "Could not load course content.",
                exception
        );
    }
}

Should the Wrapper Be Checked or Unchecked?

It depends on the application contract।

Checked Wrapper

public class CourseContentLoadException
        extends Exception {
}

Use when every caller should deliberately choose:

Retry
Fallback
Abort
Report

Unchecked Wrapper

public class CourseContentLoadException
        extends RuntimeException {
}

Use when most callers cannot recover and the failure should propagate to a boundary।


Custom Checked Exception

public class CourseImportException
        extends Exception {

    public CourseImportException(
            String message
    ) {
        super(
                message
        );
    }

    public CourseImportException(
            String message,
            Throwable cause
    ) {
        super(
                message,
                cause
        );
    }
}

Method:

public Course importCourse(
        Path path
) throws CourseImportException {
}

Caller must catch or declare it।


Custom Unchecked Exception

public class CourseRepositoryException
        extends RuntimeException {

    public CourseRepositoryException(
            String message
    ) {
        super(
                message
        );
    }

    public CourseRepositoryException(
            String message,
            Throwable cause
    ) {
        super(
                message,
                cause
        );
    }
}

Caller is not compiler-forced to catch it।


Do Not Make Every Domain Failure an Exception

Suppose enrollment method:

public boolean enroll(
        long learnerId,
        CourseCode courseCode
)

Duplicate enrollment is expected।

Returning:

false

may be sufficient।

If multiple expected outcomes exist:

public EnrollmentResult enroll(...)

may be stronger।

Exceptions are not automatically better than result types।


Checked Exception Is Not a Business Result

Weak design:

public void enroll(
        long learnerId,
        CourseCode courseCode
) throws AlreadyEnrolledException

This may be reasonable in some command APIs।

But if duplicate enrollment is frequent and expected, forcing every caller into exception handling may make normal flow noisy।

Alternative:

public EnrollmentStatus enroll(...)

with:

ENROLLED
ALREADY_ENROLLED
COURSE_NOT_AVAILABLE

Exceptions for Exceptional Contract Failure

Exception is stronger when method promises:

Complete this operation or fail exceptionally

Example:

public void requireEnrollment(
        long learnerId,
        CourseCode courseCode
)

If enrollment must exist for the operation:

throw new EnrollmentNotFoundException(...)

may be appropriate।

Method name and contract matter।


Missing Result vs Failure

Repository lookup:

Course findByCode(
        CourseCode code
)

Possible outcomes:

Found course
No matching course
Storage unavailable

These are different।

Possible design:

No match → null or Optional
Storage unavailable → exception

Do not represent both as null।

Otherwise caller cannot distinguish absence from technical failure।


A Strong Lookup Contract

public Course findByCode(
        CourseCode code
) {
    try {
        return queryDatabase(
                code
        );
    } catch (
        SQLException exception
    ) {
        throw new CourseRepositoryException(
                "Could not query course "
                + code
                + ".",
                exception
        );
    }
}

Return:

Course or null

means normal lookup result।

Exception means:

The lookup operation itself failed

Boundary Handling

Unchecked application exceptions can propagate to an outer boundary।

Example API boundary:

try {
    Course course =
            service.load(
                    code
            );

    return successResponse(
            course
    );
} catch (
        CourseNotFoundException exception
) {
    return notFoundResponse(
            exception.getMessage()
    );
} catch (
        CourseRepositoryException exception
) {
    return serverErrorResponse();
}

The boundary translates exceptions into transport-specific responses।


Do Not Catch Unchecked Exceptions Everywhere

Weak:

try {
    course.publish();
} catch (
        IllegalStateException exception
) {
    throw exception;
}

No value added।

Also weak:

try {
    course.publish();
} catch (
        RuntimeException exception
) {
    return false;
}

This may hide programming bugs such as NullPointerException

Catch only exceptions you can interpret correctly।


Never Convert Every Exception to One Boolean

public boolean publish() {
    try {
        validate();
        repository.save();
        notification.send();

        return true;
    } catch (
        Exception exception
    ) {
        return false;
    }
}

Now false may mean:

Invalid state
Database failure
Programming bug
Notification failure
Null pointer

Important distinctions disappear।


Wrapping Without Losing the Cause

Correct:

catch (
        IOException exception
) {
    throw new CourseContentLoadException(
            "Could not load content for course "
            + courseCode
            + ".",
            exception
    );
}

Weak:

catch (
        IOException exception
) {
    throw new CourseContentLoadException(
            exception.getMessage()
    );
}

The weak version loses the cause chain।


Do Not Wrap Repeatedly Without Value

Bad chain:

IOException
→ StorageException
→ ServiceException
→ ApplicationException
→ RequestException

If every layer adds only:

Operation failed

the chain becomes noisy।

Wrap only when crossing a meaningful abstraction boundary or adding useful context।


Checked Exception Propagation Example

public static String readContent(
        Path path
) throws IOException {
    return Files.readString(
            path
    );
}

Caller method:

public static void printContent(
        Path path
) throws IOException {
    String content =
            readContent(
                    path
            );

    System.out.println(
            content
    );
}

Both methods expose IOException

This is acceptable in a small low-level utility where callers understand file I/O।


Translation Example

Application service:

public final class CourseContentService {

    public String load(
            Path path
    ) {
        try {
            return Files.readString(
                    path
            );
        } catch (
            IOException exception
        ) {
            throw new CourseContentLoadException(
                    "Could not load course content.",
                    exception
            );
        }
    }
}

Here higher layers no longer need throws IOException


InterruptedException Requires Special Care

Some checked exceptions have important semantics।

InterruptedException

usually means a thread was asked to stop waiting or interrupt its work।

Weak:

catch (
        InterruptedException exception
) {
    // Ignore
}

This loses the interruption signal।

Common pattern:

catch (
        InterruptedException exception
) {
    Thread.currentThread()
            .interrupt();

    throw new IllegalStateException(
            "Operation was interrupted.",
            exception
    );
}

Thread interruption is an advanced topic, but the key rule is:

Do not silently swallow InterruptedException.


Catching Checked Exception and Returning a Default

This can be valid only when the default is genuinely correct।

Example optional configuration:

public String loadOptionalBanner(
        Path path
) {
    try {
        return Files.readString(
                path
        );
    } catch (
        NoSuchFileException exception
    ) {
        return "";
    } catch (
        IOException exception
    ) {
        throw new CourseContentLoadException(
                "Could not read banner content.",
                exception
        );
    }
}

Missing optional file has a valid fallback।

Other I/O failures do not।


API Design Example: Course Import

Importing a file can fail because:

File missing
Invalid format
Unsupported version
Storage unavailable
Duplicate course code

A strong API may use both exceptions and results।

public ImportResult importCourse(
        Path path
) throws CourseImportException

Possible design:

CourseImportException → Technical import failure
ImportResult          → Expected business outcome

For example:

IMPORTED
DUPLICATE_CODE
UNSUPPORTED_CONTENT

The exact split depends on the domain।


A Decision Framework

Choose an unchecked exception when:

  • Caller violates a programming contract
  • Object state makes the operation invalid
  • Most intermediate callers cannot recover
  • Failure should propagate to a boundary
  • Checked declaration would mostly create boilerplate
  • Lower-level failure is translated into an application runtime exception

Choose a checked exception when:

  • Caller must consciously choose a recovery path
  • Failure is central to the method’s abstraction
  • The API is low-level and resource-oriented
  • Ignoring the failure would be especially dangerous
  • Catch-or-declare improves correctness more than it adds noise

Choose a result value when:

  • Outcome is expected and common
  • Caller naturally branches on it
  • Multiple business outcomes need explicit representation
  • The operation itself completed correctly

Example Decision 1: Negative Price

Failure:
Caller passed an invalid argument

Use:

IllegalArgumentException

Unchecked।


Example Decision 2: Publish from Draft

Failure:
Current state does not allow operation

Use:

IllegalStateException

Unchecked।

Or a structured result if rejection is normal workflow।


Example Decision 3: File Read Failure

At low-level file utility:

throws IOException

may be appropriate।

At application service boundary:

CourseContentLoadException

may be more appropriate।


Example Decision 4: Course Not Found

If absence is normal:

null
Optional

If method requires existence:

CourseNotFoundException

possibly unchecked।


Example Decision 5: Duplicate Enrollment

If common expected outcome:

EnrollmentResult.ALREADY_ENROLLED

If duplicate command violates a strict API contract:

DuplicateEnrollmentException

The method’s semantics determine the choice।


Complete Example

CourseContentLoadException.java

package io.liveklass.content;

public final class CourseContentLoadException
        extends RuntimeException {

    public CourseContentLoadException(
            String message
    ) {
        super(
                message
        );
    }

    public CourseContentLoadException(
            String message,
            Throwable cause
    ) {
        super(
                message,
                cause
        );
    }
}

CourseContentService.java

package io.liveklass.content;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public final class CourseContentService {

    public String load(
            Path path
    ) {
        if (path == null) {
            throw new IllegalArgumentException(
                    "Content path is required."
            );
        }

        try {
            return Files.readString(
                    path
            );
        } catch (
                IOException exception
        ) {
            throw new CourseContentLoadException(
                    "Could not load course content from "
                    + path.getFileName()
                    + ".",
                    exception
            );
        }
    }
}

Main.java

package io.liveklass;

import io.liveklass.content.CourseContentLoadException;
import io.liveklass.content.CourseContentService;

import java.nio.file.Path;

public class Main {

    public static void main(
            String[] args
    ) {
        CourseContentService service =
                new CourseContentService();

        try {
            String content =
                    service.load(
                            Path.of(
                                    "course.md"
                            )
                    );

            System.out.println(
                    content
            );
        } catch (
                CourseContentLoadException exception
        ) {
            System.out.println(
                    "Course content is temporarily unavailable."
            );

            System.err.println(
                    exception.getMessage()
            );
        }
    }
}

Design Review

Why Is IOException Not Exposed?

The service represents course content loading, not generic file handling।

Higher layers should not depend on storage technology।


Why Is the Custom Exception Unchecked?

Most callers cannot recover from arbitrary storage failure।

The exception can propagate to an application boundary where it is logged and translated।


Why Preserve the Cause?

Developers can still inspect:

NoSuchFileException
AccessDeniedException
Storage failure

Why Is Null Path an IllegalArgumentException?

It is a caller contract violation, not a storage failure।


Common Mistakes

Assuming Checked Means Recoverable

Many checked failures have no valid local recovery।


Assuming Unchecked Means Unrecoverable

Input boundaries can recover from unchecked parsing failures।


Declaring Low-Level Exceptions Across Every Layer

Creates technology coupling and noisy signatures।


Wrapping Every Exception at Every Method

Produces meaningless exception chains।


Catching RuntimeException and Returning False

May hide real programming defects।


Making Every Business Rejection an Exception

Normal workflow becomes exception-driven।


Returning Null for Infrastructure Failure

Makes technical failure look like missing data।


Using Checked Exceptions Without Meaningful Caller Action

Creates boilerplate catches or declaration pollution।


Using Unchecked Exceptions Without Documentation

Callers may not understand required contracts।


Losing the Original Cause

Removes debugging context।


Swallowing InterruptedException

Breaks thread interruption semantics।


Practice Exercises

Exercise 1: Classify the Exception

Classify each as checked or unchecked:

IOException
IllegalArgumentException
SQLException
NullPointerException
InterruptedException
IllegalStateException

Exercise 2: Choose the Contract

Choose checked exception, unchecked exception, or result value:

  1. Negative learner ID
  2. Optional course search found nothing
  3. Required configuration file cannot be read
  4. Duplicate enrollment
  5. Repository database unavailable
  6. Course publish called from invalid state

Explain each choice।


Exercise 3: Translate an Exception

Catch:

SQLException

and throw:

CourseRepositoryException

Requirements:

  • Include course code in the message
  • Preserve the original cause
  • Make the custom exception unchecked

Exercise 4: Avoid Declaration Pollution

Refactor:

Course loadCourse(
        CourseCode code
) throws SQLException

so service callers do not depend on JDBC-specific failures।


Exercise 5: Checked Custom Exception

Create:

CourseImportException

as a checked exception।

Use it in:

Course importCourse(
        Path path
) throws CourseImportException

Exercise 6: Result vs Exception

Design a publication API with expected outcomes:

PUBLISHED
ALREADY_PUBLISHED
NO_LESSONS
NOT_APPROVED

Use a result enum instead of exceptions।


Exercise 7: Preserve Interruption

Write a catch block for:

InterruptedException

that:

  • Restores the interrupt flag
  • Wraps the failure
  • Preserves the original cause

Predict the Result

Question 1

public static void validate(
        int value
) {
    if (value < 0) {
        throw new IllegalArgumentException(
                "Negative value."
        );
    }
}

Does the caller have to catch or declare the exception?

Answer

No।

IllegalArgumentException is unchecked।


Question 2

public static String read(
        Path path
) throws IOException {
    return Files.readString(
            path
    );
}

Can the caller ignore the checked exception at compile time?

Answer

No।

The caller must catch or declare IOException


Question 3

catch (
        IOException exception
) {
    throw new CourseContentLoadException(
            "Load failed.",
            exception
    );
}

Is the original failure preserved?

Answer

Yes।

It is stored as the cause।


Question 4

A repository returns null both when:

Course does not exist
Database query failed

Is this a clear contract?

Answer

No।

Normal absence and technical failure are indistinguishable।


Question 5

Does extending RuntimeException make an exception impossible to recover from?

Answer

No।

It only makes catch-or-declare optional for the compiler।


Knowledge Check

Question 1

What makes an exception checked?

Question 2

What makes an exception unchecked?

Question 3

What does the compiler require for checked exceptions?

Question 4

Does checked always mean recoverable?

Question 5

Does unchecked always mean programming error?

Question 6

Why are validation exceptions commonly unchecked?

Question 7

What is exception declaration pollution?

Question 8

Why translate SQLException at a repository boundary?

Question 9

When is a result value better than an exception?

Question 10

When is a checked custom exception reasonable?

Question 11

When is an unchecked custom exception reasonable?

Question 12

Why must the original cause be preserved?

Question 13

Why should not-found and infrastructure failure be separate outcomes?

Question 14

Why avoid wrapping at every layer?

Question 15

What should happen when catching InterruptedException?


Knowledge Check Answers

Answer 1

It extends Exception but not RuntimeException

Answer 2

It extends RuntimeException

Answer 3

Caller must catch it or declare it using throws

Answer 4

No।

Recoverability depends on context and available actions।

Answer 5

No।

Some unchecked failures, such as invalid user input parsing, can be handled at boundaries।

Answer 6

They represent caller contract violations and forcing every caller to catch them would add noise।

Answer 7

A low-level checked exception spreads through multiple method signatures even when intermediate layers cannot handle it meaningfully।

Answer 8

It prevents database technology details from leaking into higher application layers।

Answer 9

When the outcome is expected, common, and callers naturally branch on it।

Answer 10

When every caller should consciously acknowledge and choose a recovery path।

Answer 11

When most callers cannot recover and the failure should propagate to a meaningful boundary।

Answer 12

It preserves the technical origin and complete debugging chain।

Answer 13

One is a normal lookup outcome; the other means the operation failed।

Answer 14

It creates noisy chains without adding abstraction or useful context।

Answer 15

Restore the thread interrupt flag and propagate or translate the failure appropriately।


Lesson Summary

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

  • Checked exceptions Exception extend করে but not RuntimeException
  • Unchecked exceptions RuntimeException family-এর
  • Checked exceptions callerকে catch বা declare করতে বাধ্য করে
  • Unchecked exceptions compiler-enforced নয়
  • Checked does not automatically mean recoverable
  • Unchecked does not automatically mean unrecoverable
  • IllegalArgumentException invalid method input-এর জন্য useful
  • IllegalStateException invalid operation state-এর জন্য useful
  • Validation failures সাধারণত unchecked
  • Checked exceptions low-level resource APIs-এ useful হতে পারে
  • Checked exceptions multiple layers-এ declaration pollution তৈরি করতে পারে
  • Lower-level exceptions abstraction boundaries-এ translate করা যায়
  • Custom exceptions checked বা unchecked দুটোই হতে পারে
  • Choice caller action, abstraction, and propagation needs-এর ওপর depend করে
  • Expected business outcomes result values দিয়ে represent করা যায়
  • Not-found result এবং infrastructure failure আলাদা রাখা উচিত
  • Infrastructure failureকে null বা false দিয়ে hide করা উচিত নয়
  • Exception wrapping original cause preserve করবে
  • Every layer-এ wrapping unnecessary noise তৈরি করে
  • Unchecked exceptionsও document করা উচিত
  • InterruptedException silently swallow করা উচিত নয়
  • Checked, unchecked, এবং result types complementary failure-modeling tools
  • Strong failure contract callerকে clearly বলে কোন outcomes normal এবং কোনগুলো exceptional

Next Lesson

পরবর্তী lesson:

Designing Custom Exceptions

আমরা শিখব:

  • Why create custom exception types
  • Domain vs infrastructure exceptions
  • Naming conventions
  • Checked and unchecked custom exceptions
  • Constructors
  • Preserving causes
  • Adding structured context
  • Avoiding excessive exception classes
  • Exception hierarchy design
  • Course, enrollment, and repository examples