Enums, Exceptions, and Robust Error Handling
Checked vs Unchecked Exceptions
আপনি একটি 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 করতে
RuntimeExceptionhierarchy বুঝতে- 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:
Exceptionextend করে- কিন্তু
RuntimeExceptionextend করে না - Callerকে catch বা declare করতে হয়
Example:
IOException
Unchecked Exception
An unchecked exception:
RuntimeExceptionextend করে- 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:
- Can callers meaningfully act on the failure?
- Should every caller be forced to acknowledge it?
- Is the failure part of the abstraction’s normal contract?
- Will the exception cross many layers?
- Would catch-or-declare create noise without useful recovery?
- Is the failure caused by invalid usage or broken state?
- 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:
- Negative learner ID
- Optional course search found nothing
- Required configuration file cannot be read
- Duplicate enrollment
- Repository database unavailable
- 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
Exceptionextend করে but notRuntimeException - Unchecked exceptions
RuntimeExceptionfamily-এর - Checked exceptions callerকে catch বা declare করতে বাধ্য করে
- Unchecked exceptions compiler-enforced নয়
- Checked does not automatically mean recoverable
- Unchecked does not automatically mean unrecoverable
IllegalArgumentExceptioninvalid method input-এর জন্য usefulIllegalStateExceptioninvalid 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 করা উচিত
InterruptedExceptionsilently 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