Enums, Exceptions, and Robust Error Handling
Designing Custom Exceptions
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
Java built-in exceptions অনেক common failure represent করে:
IllegalArgumentException
IllegalStateException
IOException
SQLException
কিন্তু application-এর কিছু failures domain-specific।
Examples:
Course not found
Duplicate enrollment
Course content could not be loaded
Course code already registered
Payment processing failed
Invalid course transition
সব failureকে:
IllegalStateException
দিয়ে represent করলে caller failure-এর exact meaning বুঝতে পারে না।
Custom exception meaningful type provide করে।
Example:
CourseNotFoundException
Caller can now catch:
catch (
CourseNotFoundException exception
) {
}
এই lesson-এ আমরা শিখব:
- Custom exception কেন দরকার
- Meaningful exception naming
- Checked এবং unchecked custom exceptions
- Constructors
- Cause preservation
- Structured context
- Domain এবং infrastructure exception distinction
- Exception hierarchy
- Overly broad এবং overly granular exception design avoid করা
- Course, enrollment, এবং repository examples
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- একটি custom exception class তৈরি করতে
- Checked বা unchecked base class choose করতে
- Meaningful constructors add করতে
- Original cause preserve করতে
- Domain-specific failure type design করতে
- Exception hierarchy create করতে
- Structured context safely expose করতে
- Excessive custom exceptions avoid করতে
- Boundary-level exception translation implement করতে
Why Create a Custom Exception?
Consider:
throw new IllegalStateException(
"Course not found."
);
Caller catches:
catch (
IllegalStateException exception
) {
}
But IllegalStateException may also mean:
Course has no lessons
Course is already archived
Invalid publication transition
Repository unavailable
The type does not communicate enough।
Custom exception:
throw new CourseNotFoundException(
courseCode
);
Now the failure meaning is explicit।
Exception Type Is Part of the Contract
A custom exception type communicates:
What category of failure occurred?
Message communicates:
What specific context caused it?
Example:
CourseNotFoundException
is the category।
Course was not found for code JAVA-OOP.
is the contextual message।
Both are useful।
Basic Unchecked Custom Exception
public final class CourseNotFoundException
extends RuntimeException {
public CourseNotFoundException(
String message
) {
super(
message
);
}
}
Usage:
throw new CourseNotFoundException(
"Course was not found."
);
Because it extends:
RuntimeException
caller is not compiler-forced to catch or declare it।
Basic Checked Custom Exception
public final class CourseImportException
extends Exception {
public CourseImportException(
String message
) {
super(
message
);
}
}
Method:
public Course importCourse(
Path path
) throws CourseImportException {
}
Caller must catch or declare it।
Choosing Checked or Unchecked
Use an unchecked custom exception when:
- Failure represents invalid usage
- Failure represents invalid object state
- Most callers cannot recover
- Exception should propagate to an application boundary
- Checked declarations would create unnecessary noise
Use a checked custom exception when:
- Every caller should consciously acknowledge the failure
- Caller has meaningful recovery options
- The operation is low-level or resource-oriented
- Catch-or-declare improves correctness
Use a result type when:
- Failure is expected and common
- Caller naturally branches on the outcome
- The operation itself completed correctly
Naming Conventions
Custom exception names should usually end with:
Exception
Good:
CourseNotFoundException
DuplicateEnrollmentException
CourseRepositoryException
CourseImportException
InvalidCourseTransitionException
Weak:
CourseError
Problem
Failure
InvalidThing
The name should describe the failure category clearly।
Avoid Repeating Exception Meaninglessly
Weak:
CourseException
This is too broad unless it is an intentional base class।
Stronger:
CourseNotFoundException
CoursePublicationException
CourseRepositoryException
Use broad names only as hierarchy parents।
Standard Constructors
A useful custom exception often includes:
public CourseRepositoryException(
String message
) {
super(
message
);
}
and:
public CourseRepositoryException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
The second constructor preserves lower-level failure।
Complete Basic Exception
public final class CourseRepositoryException
extends RuntimeException {
public CourseRepositoryException(
String message
) {
super(
message
);
}
public CourseRepositoryException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}
Usage:
catch (
SQLException exception
) {
throw new CourseRepositoryException(
"Could not load course "
+ courseCode
+ ".",
exception
);
}
Preserve the Original Cause
Weak:
catch (
SQLException exception
) {
throw new CourseRepositoryException(
"Database operation failed."
);
}
The original cause is lost।
Better:
catch (
SQLException exception
) {
throw new CourseRepositoryException(
"Database operation failed.",
exception
);
}
Now stack trace includes:
Caused by: SQLException
Why Cause Preservation Matters
Without cause:
CourseRepositoryException
may hide whether the original failure was:
Connection timeout
Constraint violation
Authentication failure
Syntax error
Network interruption
Cause chain retains technical diagnosis while higher-level type communicates application meaning।
Custom Exception with Domain Context
public final class CourseNotFoundException
extends RuntimeException {
private final CourseCode courseCode;
public CourseNotFoundException(
CourseCode courseCode
) {
super(
"Course was not found for code "
+ courseCode
+ "."
);
this.courseCode =
courseCode;
}
public CourseCode getCourseCode() {
return courseCode;
}
}
Caller can access structured context:
exception.getCourseCode()
without parsing the message।
Why Structured Context Is Better Than Parsing Text
Weak:
String message =
exception.getMessage();
// Extract course code from text
Messages may change।
Structured field:
CourseCode courseCode =
exception.getCourseCode();
is stable and type-safe।
Keep Exception Context Immutable
private final CourseCode courseCode;
Custom context fields should usually be immutable।
Avoid adding setters:
setCourseCode(...)
Exception meaning should not change after creation।
Do Not Store Sensitive Data
Avoid fields or messages containing:
Password
Access token
Payment card number
Private identity data
Full database connection string
Secret API key
Exceptions often reach logs and monitoring systems।
Store only safe diagnostic context।
Domain Exception vs Infrastructure Exception
Domain Exception
Represents a business or domain rule failure।
Examples:
CourseNotFoundException
DuplicateEnrollmentException
InvalidCourseTransitionException
Infrastructure Exception
Represents failure in storage, network, or external systems।
Examples:
CourseRepositoryException
CourseContentStorageException
PaymentGatewayException
These categories may be handled differently at boundaries।
Example Domain Exception
public final class InvalidCourseTransitionException
extends RuntimeException {
private final CourseStatus currentStatus;
private final CourseStatus targetStatus;
public InvalidCourseTransitionException(
CourseStatus currentStatus,
CourseStatus targetStatus
) {
super(
"Course cannot transition from "
+ currentStatus
+ " to "
+ targetStatus
+ "."
);
this.currentStatus =
currentStatus;
this.targetStatus =
targetStatus;
}
public CourseStatus getCurrentStatus() {
return currentStatus;
}
public CourseStatus getTargetStatus() {
return targetStatus;
}
}
Using the Transition Exception
public void changeStatus(
CourseStatus targetStatus
) {
if (targetStatus == null) {
throw new IllegalArgumentException(
"Target status is required."
);
}
if (
!status.canTransitionTo(
targetStatus
)
) {
throw new InvalidCourseTransitionException(
status,
targetStatus
);
}
status =
targetStatus;
}
Here:
Null target → General argument contract violation
Invalid transition → Domain-specific failure
Example Infrastructure Exception
public final class CourseContentStorageException
extends RuntimeException {
public CourseContentStorageException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}
Usage:
try {
return Files.readString(
path
);
} catch (
IOException exception
) {
throw new CourseContentStorageException(
"Could not read content for course "
+ courseCode
+ ".",
exception
);
}
Exception Translation Between Layers
Consider layers:
File system
↓
Storage adapter
↓
Application service
↓
API boundary
Low-level failure:
IOException
Storage adapter translates to:
CourseContentStorageException
API boundary translates to:
HTTP 500 response
Each layer communicates in its own abstraction।
Avoid Leaking Technology Details
Weak repository contract:
Course findByCode(
CourseCode code
) throws SQLException;
Higher layers now know JDBC details।
Stronger repository implementation:
public Course findByCode(
CourseCode code
) {
try {
return executeQuery(
code
);
} catch (
SQLException exception
) {
throw new CourseRepositoryException(
"Could not query course "
+ code
+ ".",
exception
);
}
}
Exception Hierarchy
Sometimes related exceptions benefit from a shared parent।
Example:
public abstract class CourseException
extends RuntimeException {
protected CourseException(
String message
) {
super(
message
);
}
protected CourseException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}
Child:
public final class CourseNotFoundException
extends CourseException {
}
Another child:
public final class CourseRepositoryException
extends CourseException {
}
Why Use a Shared Parent?
Boundary can catch all course-related failures:
catch (
CourseException exception
) {
}
Or specific ones first:
catch (
CourseNotFoundException exception
) {
} catch (
CourseException exception
) {
}
A hierarchy is useful when failures share handling behavior।
Catch Ordering with Custom Hierarchy
Correct:
try {
service.load(
code
);
} catch (
CourseNotFoundException exception
) {
// 404
} catch (
CourseException exception
) {
// General course failure
}
Specific child must come first।
Do Not Create a Hierarchy Without a Handling Need
Weak design:
ApplicationException
├── DomainException
│ ├── CourseException
│ │ ├── CourseStateException
│ │ │ └── InvalidCourseTransitionException
If no caller catches these parent levels meaningfully, the hierarchy adds complexity without value।
Start simple।
Add a parent when shared handling actually exists।
One Exception per Message Is Too Much
Avoid creating exceptions such as:
CourseTitleBlankException
CourseIdNegativeException
CourseHasZeroLessonsException
CourseAlreadyPublishedException
for every validation sentence।
Built-in exceptions may be sufficient:
IllegalArgumentException
IllegalStateException
Create custom types when callers need to distinguish the category programmatically or when the type expresses an important domain boundary।
Too Few Exception Types Is Also Weak
Weak:
throw new ApplicationException(
"Something failed."
);
Everything becomes indistinguishable।
Balance:
- Built-in exceptions for generic contracts
- Custom domain exceptions for meaningful categories
- Custom infrastructure exceptions at abstraction boundaries
- Result types for expected outcomes
Custom Exceptions Should Add Meaning
A custom exception should provide at least one of:
- Meaningful failure category
- Structured context
- Abstraction translation
- Shared handling type
- Stable application contract
If it only renames:
RuntimeException
without adding meaning, it may not be worthwhile।
Course Not Found: Result or Exception?
Two methods may have different contracts।
Optional Lookup
public Course findByCode(
CourseCode code
)
Possible:
Return Course
Return null
Absence is expected।
Required Lookup
public Course requireByCode(
CourseCode code
)
Possible:
Course course =
findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
return course;
Method name communicates that existence is required।
Complete CourseNotFoundException
package io.liveklass.course;
public final class CourseNotFoundException
extends RuntimeException {
private final CourseCode courseCode;
public CourseNotFoundException(
CourseCode courseCode
) {
super(
"Course was not found for code "
+ courseCode
+ "."
);
this.courseCode =
courseCode;
}
public CourseCode getCourseCode() {
return courseCode;
}
}
Catalog with Optional and Required Lookup
public final class CourseCatalog {
private final Map<CourseCode, Course> coursesByCode;
public CourseCatalog() {
this.coursesByCode =
new HashMap<>();
}
public Course findByCode(
CourseCode code
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
return coursesByCode.get(
code
);
}
public Course requireByCode(
CourseCode code
) {
Course course =
findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
return course;
}
}
Duplicate Enrollment Exception
public final class DuplicateEnrollmentException
extends RuntimeException {
private final long learnerId;
private final CourseCode courseCode;
public DuplicateEnrollmentException(
long learnerId,
CourseCode courseCode
) {
super(
"Learner "
+ learnerId
+ " is already enrolled in course "
+ courseCode
+ "."
);
this.learnerId =
learnerId;
this.courseCode =
courseCode;
}
public long getLearnerId() {
return learnerId;
}
public CourseCode getCourseCode() {
return courseCode;
}
}
Should Duplicate Enrollment Throw?
It depends on the contract।
Result-Based Contract
EnrollmentResult enroll(...)
Use when duplicate enrollment is expected and frequent।
Exception-Based Contract
void enroll(...)
Use when method promises to create enrollment and duplicate command violates that contract।
Do not choose exception only because it sounds more serious।
Checked Import Exception Example
Course import may require caller action।
public final class CourseImportException
extends Exception {
private final Path sourcePath;
public CourseImportException(
Path sourcePath,
String message
) {
super(
message
);
this.sourcePath =
sourcePath;
}
public CourseImportException(
Path sourcePath,
String message,
Throwable cause
) {
super(
message,
cause
);
this.sourcePath =
sourcePath;
}
public Path getSourcePath() {
return sourcePath;
}
}
Method:
public Course importCourse(
Path path
) throws CourseImportException {
}
Import Service Example
public final class CourseImportService {
public String importContent(
Path path
) throws CourseImportException {
if (path == null) {
throw new IllegalArgumentException(
"Import path is required."
);
}
try {
return Files.readString(
path
);
} catch (
IOException exception
) {
throw new CourseImportException(
path,
"Could not import course content.",
exception
);
}
}
}
Caller must explicitly handle or declare CourseImportException।
Structured Error Codes
Sometimes exception type alone is not enough।
Possible:
public enum EnrollmentErrorCode {
ALREADY_ENROLLED,
COURSE_NOT_AVAILABLE,
LEARNER_SUSPENDED
}
Exception:
public final class EnrollmentException
extends RuntimeException {
private final EnrollmentErrorCode code;
public EnrollmentException(
EnrollmentErrorCode code,
String message
) {
super(
message
);
this.code = code;
}
public EnrollmentErrorCode getCode() {
return code;
}
}
Boundary can map code to a stable response।
Type vs Error Code
Possible designs:
Separate Exception Types
DuplicateEnrollmentException
CourseUnavailableException
LearnerSuspendedException
Good when each failure has distinct handling and context।
One Exception with Error Code
EnrollmentException
+ EnrollmentErrorCode
Good when failures share a boundary and stable code mapping।
Avoid creating both a deep hierarchy and many codes without a clear need।
Exception Message Is Not an API Code
Do not write:
if (
exception.getMessage()
.contains(
"already enrolled"
)
) {
}
Messages are for humans and diagnostics।
Use:
- Exception type
- Enum error code
- Structured field
for programmatic decisions।
API Boundary Mapping Example
try {
enrollmentService.enroll(
learnerId,
courseCode
);
return createdResponse();
} catch (
DuplicateEnrollmentException exception
) {
return conflictResponse(
"ALREADY_ENROLLED",
exception.getMessage()
);
} catch (
CourseNotFoundException exception
) {
return notFoundResponse(
"COURSE_NOT_FOUND",
exception.getMessage()
);
} catch (
CourseRepositoryException exception
) {
logError(
exception
);
return serverErrorResponse();
}
Different exception categories become different external responses।
Do Not Return Raw Internal Messages Automatically
Exception message may contain safe diagnostic context, but boundary should still decide what external message to expose।
Internal:
Could not query course JAVA-OOP using PostgreSQL.
External:
Course information is temporarily unavailable.
Exception type helps mapping without exposing implementation details।
Serialization Warning
Custom exceptions are normal objects, but you usually should not serialize the entire exception directly into an API response।
Avoid exposing:
- Stack trace
- Cause chain
- Internal fields
- Class names
- Storage paths
Create a dedicated error response model instead।
Logging Custom Exceptions
Log once at a boundary that owns failure reporting।
Example:
catch (
CourseRepositoryException exception
) {
logger.error(
"Course repository operation failed.",
exception
);
return serverErrorResponse();
}
Avoid logging and rethrowing at every layer unless each log adds unique operational value।
Constructor Validation in Exceptions
Suppose:
public CourseNotFoundException(
CourseCode courseCode
)
Should it accept null?
Usually no।
this.courseCode =
Objects.requireNonNull(
courseCode,
"Course code is required."
);
But ensure message construction does not happen before validation।
Example:
public CourseNotFoundException(
CourseCode courseCode
) {
super(
createMessage(
courseCode
)
);
this.courseCode =
courseCode;
}
private static String createMessage(
CourseCode courseCode
) {
if (courseCode == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
return "Course was not found for code "
+ courseCode
+ ".";
}
In practice, exception constructors should remain simple।
Keep Custom Exceptions Lightweight
Exceptions should not:
- Query databases
- Call services
- Read files
- Send notifications
- Perform expensive calculations
They should mainly hold:
- Type
- Message
- Cause
- Safe context
Avoid Mutable Collections Inside Exceptions
Weak:
private final List<String> validationErrors;
and returning the same mutable list।
Better:
this.validationErrors =
List.copyOf(
validationErrors
);
Getter:
public List<String> getValidationErrors() {
return validationErrors;
}
Because stored list is already immutable।
Validation Exception with Multiple Errors
public final class CourseValidationException
extends RuntimeException {
private final List<String> errors;
public CourseValidationException(
List<String> errors
) {
super(
createMessage(
errors
)
);
if (
errors == null
|| errors.isEmpty()
) {
throw new IllegalArgumentException(
"Validation errors are required."
);
}
this.errors =
List.copyOf(
errors
);
}
public List<String> getErrors() {
return errors;
}
private static String createMessage(
List<String> errors
) {
if (
errors == null
|| errors.isEmpty()
) {
return "Course validation failed.";
}
return "Course validation failed with "
+ errors.size()
+ " error(s).";
}
}
This can represent aggregate validation failure।
Exception or Validation Result?
For interactive forms, returning all validation errors may be normal behavior।
Possible:
ValidationResult
instead of throwing।
Custom validation exception is more appropriate when:
- Method contract requires valid input
- Validation failure aborts an operation
- Boundary centrally translates the exception
- Caller does not need local branching on every field
Again, contract matters।
Complete Example: Course Service
CourseNotFoundException.java
package io.liveklass.course;
public final class CourseNotFoundException
extends RuntimeException {
private final CourseCode courseCode;
public CourseNotFoundException(
CourseCode courseCode
) {
super(
"Course was not found for code "
+ courseCode
+ "."
);
this.courseCode =
courseCode;
}
public CourseCode getCourseCode() {
return courseCode;
}
}
CourseRepositoryException.java
package io.liveklass.course;
public final class CourseRepositoryException
extends RuntimeException {
public CourseRepositoryException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}
CourseRepository.java
package io.liveklass.course;
public interface CourseRepository {
Course findByCode(
CourseCode courseCode
);
}
CourseService.java
package io.liveklass.course;
public final class CourseService {
private final CourseRepository repository;
public CourseService(
CourseRepository repository
) {
if (repository == null) {
throw new IllegalArgumentException(
"Course repository is required."
);
}
this.repository =
repository;
}
public Course requireCourse(
CourseCode courseCode
) {
if (courseCode == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
Course course =
repository.findByCode(
courseCode
);
if (course == null) {
throw new CourseNotFoundException(
courseCode
);
}
return course;
}
}
Boundary Handling Example
public String handleCourseRequest(
String rawCourseCode
) {
try {
CourseCode courseCode =
new CourseCode(
rawCourseCode
);
Course course =
courseService.requireCourse(
courseCode
);
return course.getTitle();
} catch (
IllegalArgumentException exception
) {
return "Invalid course code.";
} catch (
CourseNotFoundException exception
) {
return exception.getMessage();
} catch (
CourseRepositoryException exception
) {
return "Course information is temporarily unavailable.";
}
}
Design Review
Why Use IllegalArgumentException for Invalid Code?
It is a general caller contract violation।
No custom exception is necessary unless callers need a distinct invalid-code category।
Why Use CourseNotFoundException?
Required lookup failed and boundary needs to map it distinctly।
Why Use CourseRepositoryException?
Technical persistence failure should not look like normal absence।
Why Keep Cause in Repository Exception?
Operational debugging still needs the original storage failure।
Common Mistakes
Creating a Custom Exception for Every Validation Message
Produces excessive classes without meaningful handling differences।
Using One Generic Application Exception
Makes failure categories indistinguishable।
Forgetting the Cause Constructor
Prevents proper exception wrapping।
Parsing Exception Messages
Messages are not stable programmatic contracts।
Storing Sensitive Context
May leak secrets through logs।
Adding Mutable Context
Exception meaning can change after creation।
Building Deep Hierarchies Without Shared Handling
Adds complexity without benefit।
Throwing Checked Exceptions That No Caller Can Handle
Creates declaration pollution।
Making Expected Business Outcomes Exception-Driven
Makes normal control flow noisy।
Logging at Every Layer
Creates duplicate logs।
Exposing Internal Exception Objects Directly to API Clients
Leaks implementation details।
Doing Work Inside Exception Constructors
Exceptions should remain lightweight।
Practice Exercises
Exercise 1: Course Not Found
Create:
CourseNotFoundException
Requirements:
- Extend
RuntimeException - Store
CourseCode - Generate a clear message
- Provide getter
Exercise 2: Repository Exception
Create:
CourseRepositoryException
Requirements:
- Extend
RuntimeException - Support message-only constructor
- Support message-and-cause constructor
Exercise 3: Checked Import Exception
Create:
CourseImportException
Requirements:
- Extend
Exception - Store source
Path - Preserve optional cause
- Provide getter
Exercise 4: Invalid Transition
Create:
InvalidCourseTransitionException
Store:
Current status
Target status
Use it in a course status change method।
Exercise 5: Choose Built-In or Custom
Choose the best exception:
- Blank course title
- Course not found during required lookup
- Course publishing from invalid state
- Database query failure
- Negative learner ID
- Duplicate enrollment requiring distinct API handling
Explain each choice।
Exercise 6: Error Code Design
Create:
EnrollmentException
with:
EnrollmentErrorCode
Values:
ALREADY_ENROLLED
COURSE_NOT_AVAILABLE
LEARNER_SUSPENDED
Exercise 7: Preserve the Cause
Catch:
IOException
and throw:
CourseContentStorageException
Include course code and preserve the cause।
Exercise 8: Simplify a Hierarchy
Given:
ApplicationException
DomainException
LearningException
CourseException
CourseStateException
CoursePublishStateException
Decide which levels can be removed and explain why।
Predict the Result
Question 1
public final class TestException
extends RuntimeException {
}
Must callers catch or declare it?
Answer
No।
It is unchecked।
Question 2
public final class TestException
extends Exception {
}
Must callers catch or declare it?
Answer
Yes।
It is checked।
Question 3
throw new CourseRepositoryException(
"Load failed.",
ioException
);
Where is ioException stored?
Answer
As the exception cause।
It can be accessed with:
getCause()
Question 4
Can two custom exception types have the same message?
Answer
Yes।
Programmatic distinction comes from the type, not only the message।
Question 5
Should caller logic search exception message text to decide an HTTP status?
Answer
No।
Use exception type, structured field, or stable error code।
Knowledge Check
Question 1
Why create a custom exception?
Question 2
When should it extend RuntimeException?
Question 3
When should it extend Exception?
Question 4
Why include a cause constructor?
Question 5
What is exception translation?
Question 6
What is structured exception context?
Question 7
Why should context fields be immutable?
Question 8
Why should sensitive data not be stored in exceptions?
Question 9
When is a shared exception parent useful?
Question 10
Why avoid one exception class per validation message?
Question 11
Why avoid one generic exception for all failures?
Question 12
Why should messages not be parsed programmatically?
Question 13
When is a result type better than a custom exception?
Question 14
Why should custom exception constructors stay lightweight?
Question 15
What should an API boundary expose instead of a raw exception?
Knowledge Check Answers
Answer 1
To communicate a meaningful failure category, add context, translate abstractions, or enable distinct handling।
Answer 2
When callers are not compiler-forced to handle it, most intermediate layers cannot recover, or the failure represents a contract or application-level failure।
Answer 3
When every caller should deliberately acknowledge and choose a recovery path।
Answer 4
To preserve the original technical failure and stack trace chain।
Answer 5
Catching a lower-level exception and throwing one meaningful to the current abstraction।
Answer 6
Typed fields such as course code, learner ID, or current status stored on the exception।
Answer 7
The failure meaning should remain stable after creation।
Answer 8
Exceptions often reach logs, monitoring, and support tools।
Answer 9
When multiple related failures share meaningful handling behavior।
Answer 10
It creates excessive classes without distinct contracts or handling needs।
Answer 11
Callers cannot distinguish important failure categories।
Answer 12
Messages are human-readable and may change; they are not stable contracts।
Answer 13
When the outcome is expected, common, and part of normal business flow।
Answer 14
They should represent failure, not perform services, I/O, or expensive work।
Answer 15
A safe dedicated error response with stable code and user-appropriate message।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Custom exception meaningful application failure types তৈরি করে
- Exception type এবং message different purposes serve করে
- Unchecked custom exceptions
RuntimeExceptionextend করে - Checked custom exceptions
Exceptionextend করে - Checked or unchecked choice caller action এবং API contract-এর ওপর depend করে
- Exception names usually
Exceptionsuffix ব্যবহার করে - Cause constructor lower-level failure preserve করে
- Structured context message parsing-এর চেয়ে safer
- Exception context immutable হওয়া উচিত
- Sensitive information exception messages বা fields-এ রাখা উচিত নয়
- Domain exceptions business rule failures represent করে
- Infrastructure exceptions storage, network, or external-system failures represent করে
- Exception translation abstraction boundaries protect করে
- Shared parent exception common handling support করতে পারে
- Deep hierarchy shared handling ছাড়া unnecessary
- Every validation message-এর জন্য custom class প্রয়োজন নেই
- One generic exception সব failuresকে indistinguishable করে
- Optional lookup এবং required lookup different contracts রাখতে পারে
- Expected business outcomes result types দিয়ে model করা যেতে পারে
- Stable error codes programmatic handling improve করে
- Exception messagesকে API codes হিসেবে ব্যবহার করা উচিত নয়
- API boundaries raw exception serialize না করে safe error response তৈরি করবে
- Custom exceptions lightweight এবং focused হওয়া উচিত
- Original cause, meaningful type, এবং safe context strong exception design-এর foundation
Next Lesson
পরবর্তী lesson:
Validation and Error-Handling Strategies
আমরা শিখব:
- Input validation layers
- Constructor validation
- Domain validation
- Request validation
- Fail-fast validation
- Collecting multiple validation errors
- Exceptions vs validation results
- Boundary translation
- Avoiding duplicated validation
- Building consistent error responses