Introduction
Applications rarely execute under perfect conditions.
A user may enter invalid data, a file may be missing, a network request may fail, a database connection may be unavailable, or application code may receive an unexpected value.
If these situations are not handled properly, the application may terminate unexpectedly.
Java provides a built-in exception handling mechanism that allows developers to detect exceptional situations, handle errors appropriately, clean up resources, and keep applications reliable.
For example:
1int result = 10 / 0;
This produces an ArithmeticException.
Instead of allowing the exception to terminate the application, you can handle it:
1try { 2 int result = 10 / 0; 3 System.out.println(result); 4} catch (ArithmeticException e) { 5 System.out.println("Division by zero is not allowed."); 6}
Output:
1Division by zero is not allowed.
Exception handling is important in:
- Java backend development
- Spring Boot applications
- REST APIs
- Database applications
- File processing
- Network applications
- Enterprise software
In this tutorial, you will learn:
- What exceptions are
- Java exception hierarchy
- Checked and unchecked exceptions
trycatchfinallythrowthrows- Try-with-resources
- Custom exceptions
- Exception propagation
- Multiple catch blocks
- Best practices
- Real-world exception handling examples
What Is an Exception?
An exception is an object that represents an abnormal condition that interrupts the normal flow of program execution.
Consider:
1public class Main { 2 3 public static void main(String[] args) { 4 5 int number = 10; 6 int result = number / 0; 7 8 System.out.println(result); 9 } 10}
The program cannot perform the division because dividing an integer by zero is invalid.
Java throws:
1java.lang.ArithmeticException: / by zero
An exception can occur because of:
- Invalid user input
- Invalid arithmetic operations
- Missing files
- Network failures
- Database failures
- Invalid object state
- Null references
- Invalid array indexes
The purpose of exception handling is not to hide errors. It is to handle expected exceptional conditions appropriately and provide enough information to diagnose unexpected failures.
Exception Hierarchy
Java's exception hierarchy begins with Throwable.
1Throwable 2├── Error 3│ ├── OutOfMemoryError 4│ └── StackOverflowError 5│ 6└── Exception 7 ├── RuntimeException 8 │ ├── NullPointerException 9 │ ├── ArithmeticException 10 │ ├── IllegalArgumentException 11 │ └── IndexOutOfBoundsException 12 │ 13 └── Other checked exceptions 14 ├── IOException 15 └── SQLException
Throwable
Throwable is the root class for objects that can be thrown by Java's exception mechanism.
It has two major branches:
1Error 2Exception
Error
Error generally represents serious problems associated with the JVM or runtime environment.
Examples include:
1OutOfMemoryError 2StackOverflowError
Application code generally should not attempt to recover from these errors.
Exception
Exception represents conditions that application code may often be able to handle.
Examples include:
1IOException 2SQLException 3RuntimeException
Checked and Unchecked Exceptions
Java broadly divides exceptions into checked exceptions and unchecked exceptions.
Checked Exceptions
Checked exceptions are exceptions other than RuntimeException and its subclasses.
The compiler requires checked exceptions to be either:
- Handled with
try-catch - Declared using
throws
For example:
1import java.io.IOException; 2 3public class FileService { 4 5 public static void readFile() throws IOException { 6 // File operation 7 } 8}
IOException is a checked exception.
Unchecked Exceptions
Unchecked exceptions are subclasses of RuntimeException.
Examples include:
1NullPointerException 2IllegalArgumentException 3ArithmeticException 4NumberFormatException 5IndexOutOfBoundsException
The compiler does not require these exceptions to be explicitly caught or declared.
Comparison
| Feature | Checked Exception | Unchecked Exception |
|---|---|---|
| Compiler requires handling/declaration | Yes | No |
| Base category | Exception excluding RuntimeException | RuntimeException |
| Common cause | External/environmental conditions | Programming or validation errors |
| Example | IOException | NullPointerException |
Must use try-catch? | Not necessarily, throws can be used | No |
The distinction is important, but simply categorizing an exception does not tell you whether catching it is a good design choice. Handle an exception where the application can actually make a useful decision.
try Block
The try block contains code that may throw an exception.
Basic syntax:
1try { 2 // Code that may throw an exception 3}
A try block normally needs an associated catch, finally, or both.
Example:
1public class TryExample { 2 3 public static void main(String[] args) { 4 5 try { 6 7 int result = 20 / 0; 8 9 System.out.println(result); 10 11 } catch (ArithmeticException e) { 12 13 System.out.println( 14 "Cannot divide by zero." 15 ); 16 } 17 } 18}
Output:
1Cannot divide by zero.
When the exception occurs, Java stops executing the remaining statements in the try block and searches for a matching handler.
catch Block
A catch block handles an exception thrown by the associated try block.
Syntax:
1catch (ExceptionType exception) { 2 // Handle exception 3}
Example:
1public class CatchExample { 2 3 public static void main(String[] args) { 4 5 try { 6 7 int[] numbers = {10, 20, 30}; 8 9 System.out.println(numbers[5]); 10 11 } catch (ArrayIndexOutOfBoundsException e) { 12 13 System.out.println( 14 "The requested index does not exist." 15 ); 16 } 17 } 18}
Output:
1The requested index does not exist.
Getting Exception Information
An exception object provides useful diagnostic information.
1try { 2 3 int result = 10 / 0; 4 5} catch (ArithmeticException e) { 6 7 System.out.println(e.getMessage()); 8 System.out.println(e.getClass().getSimpleName()); 9}
Useful methods include:
1e.getMessage() 2e.getClass() 3e.printStackTrace()
In production applications, stack traces are normally sent to an appropriate logging system rather than printed directly to standard output.
Multiple catch Blocks
A single try block can have multiple catch blocks.
1public class MultipleCatchExample { 2 3 public static void main(String[] args) { 4 5 try { 6 7 String value = null; 8 9 System.out.println(value.length()); 10 11 } catch (ArithmeticException e) { 12 13 System.out.println("Arithmetic error."); 14 15 } catch (NullPointerException e) { 16 17 System.out.println("Value cannot be null."); 18 } 19 } 20}
Output:
1Value cannot be null.
Java checks the handlers in order.
Therefore, specific exceptions should appear before broader exception types.
For example:
1try { 2 3 // Code 4 5} catch (ArithmeticException e) { 6 7 // Specific exception 8 9} catch (RuntimeException e) { 10 11 // More general exception 12}
Do not place:
1catch (Exception e)
before:
1catch (ArithmeticException e)
because the broader handler would already match the more specific exception.
Multi-Catch
When different exceptions require exactly the same handling, Java supports multi-catch.
1try { 2 3 // Risky operation 4 5} catch (NumberFormatException | ArithmeticException e) { 6 7 System.out.println( 8 "Invalid numeric operation." 9 ); 10}
This can reduce duplicate handling code.
finally Block
The finally block is used for cleanup code that should normally execute whether an exception occurs or not.
Example:
1public class FinallyExample { 2 3 public static void main(String[] args) { 4 5 try { 6 7 System.out.println("Executing operation."); 8 9 } catch (Exception e) { 10 11 System.out.println("Handling error."); 12 13 } finally { 14 15 System.out.println("Cleanup completed."); 16 } 17 } 18}
Output:
1Executing operation. 2Cleanup completed.
A finally block normally executes even when an exception is thrown.
However, it is not literally guaranteed in every possible JVM termination scenario, such as abrupt JVM termination.
try-catch-finally
You can combine all three constructs.
1public class Example { 2 3 public static void main(String[] args) { 4 5 try { 6 7 int[] values = {10, 20, 30}; 8 9 System.out.println(values[10]); 10 11 } catch (ArrayIndexOutOfBoundsException e) { 12 13 System.out.println( 14 "Invalid array index." 15 ); 16 17 } finally { 18 19 System.out.println( 20 "Operation completed." 21 ); 22 } 23 } 24}
Output:
1Invalid array index. 2Operation completed.
throw Keyword
The throw keyword explicitly throws an exception.
Syntax:
1throw new ExceptionType("message");
Example:
1public class AgeValidator { 2 3 public static void validate(int age) { 4 5 if (age < 18) { 6 7 throw new IllegalArgumentException( 8 "Age must be at least 18." 9 ); 10 } 11 12 System.out.println( 13 "Age validation successful." 14 ); 15 } 16 17 public static void main(String[] args) { 18 19 validate(15); 20 } 21}
The throw statement is useful when your application detects an invalid condition.
For example:
1if (amount <= 0) { 2 throw new IllegalArgumentException( 3 "Amount must be greater than zero." 4 ); 5}
throws Keyword
The throws keyword declares exceptions that a method may pass to its caller.
Example:
1import java.io.IOException; 2 3public class FileService { 4 5 public static void readFile() 6 throws IOException { 7 8 throw new IOException( 9 "Unable to read the file." 10 ); 11 } 12 13 public static void main(String[] args) { 14 15 try { 16 17 readFile(); 18 19 } catch (IOException e) { 20 21 System.out.println( 22 "File error: " 23 + e.getMessage() 24 ); 25 } 26 } 27}
Output:
1File error: Unable to read the file.
throw vs throws
throw | throws |
|---|---|
| Actually throws an exception | Declares possible exceptions |
| Used inside a method | Used in method declaration |
| Throws one exception object at a time | Can declare multiple exception types |
Example: throw new IOException() | Example: throws IOException |
Exception Propagation
If a method does not handle an exception, the exception can propagate to its caller.
Consider:
1public class PropagationExample { 2 3 static void methodC() { 4 5 int result = 10 / 0; 6 7 System.out.println(result); 8 } 9 10 static void methodB() { 11 12 methodC(); 13 } 14 15 static void methodA() { 16 17 methodB(); 18 } 19 20 public static void main(String[] args) { 21 22 try { 23 24 methodA(); 25 26 } catch (ArithmeticException e) { 27 28 System.out.println( 29 "Arithmetic error handled in main." 30 ); 31 } 32 } 33}
The flow is:
1main() 2 ↓ 3methodA() 4 ↓ 5methodB() 6 ↓ 7methodC() 8 ↓ 9Exception 10 ↓ 11methodB() does not handle it 12 ↓ 13methodA() does not handle it 14 ↓ 15main() handles it
This is called exception propagation.
Custom Exceptions
Java provides many built-in exception classes, but application-specific domains sometimes benefit from custom exceptions.
For example, a banking application might define:
1class InsufficientBalanceException 2 extends RuntimeException { 3 4 public InsufficientBalanceException( 5 String message 6 ) { 7 super(message); 8 } 9}
Now it can be used by a service:
1class BankAccount { 2 3 private double balance; 4 5 public BankAccount(double balance) { 6 7 if (balance < 0) { 8 9 throw new IllegalArgumentException( 10 "Initial balance cannot be negative." 11 ); 12 } 13 14 this.balance = balance; 15 } 16 17 public void withdraw(double amount) { 18 19 if (amount <= 0) { 20 21 throw new IllegalArgumentException( 22 "Withdrawal amount must be positive." 23 ); 24 } 25 26 if (amount > balance) { 27 28 throw new InsufficientBalanceException( 29 "Insufficient account balance." 30 ); 31 } 32 33 balance -= amount; 34 } 35 36 public double getBalance() { 37 38 return balance; 39 } 40}
Usage:
1public class Main { 2 3 public static void main(String[] args) { 4 5 BankAccount account = 6 new BankAccount(5000); 7 8 try { 9 10 account.withdraw(7000); 11 12 } catch (InsufficientBalanceException e) { 13 14 System.out.println( 15 e.getMessage() 16 ); 17 } 18 19 System.out.println( 20 "Balance: ₹" + account.getBalance() 21 ); 22 } 23}
Output:
1Insufficient account balance. 2Balance: ₹5000.0
This is often cleaner than throwing a generic Exception.
When Should You Create a Custom Exception?
A custom exception is useful when the exception represents a meaningful business or domain condition.
Examples:
1InsufficientBalanceException 2ProductOutOfStockException 3InvalidOrderException 4UserNotFoundException 5DuplicateUsernameException 6InvalidTransactionException
Avoid creating custom exceptions simply to create more classes.
The exception should communicate something meaningful to the application.
Try-With-Resources
When working with resources such as files, streams, sockets, or database resources, Java provides try-with-resources.
Resources implementing AutoCloseable can be automatically closed.
Example:
1import java.io.BufferedReader; 2import java.io.FileReader; 3import java.io.IOException; 4 5public class FileReaderExample { 6 7 public static void main(String[] args) { 8 9 try ( 10 BufferedReader reader = 11 new BufferedReader( 12 new FileReader("data.txt") 13 ) 14 ) { 15 16 String line; 17 18 while ((line = reader.readLine()) != null) { 19 20 System.out.println(line); 21 } 22 23 } catch (IOException e) { 24 25 System.err.println( 26 "Unable to read file: " 27 + e.getMessage() 28 ); 29 } 30 } 31}
The reader is automatically closed when the try block finishes.
This is generally preferable to manually closing the resource inside finally.
Why Try-With-Resources Is Better
Older code may manually close resources:
1BufferedReader reader = null; 2 3try { 4 5 reader = new BufferedReader( 6 new FileReader("data.txt") 7 ); 8 9} finally { 10 11 if (reader != null) { 12 reader.close(); 13 } 14}
This approach is verbose and can introduce additional cleanup problems.
Modern Java code should generally prefer:
1try (BufferedReader reader = 2 new BufferedReader( 3 new FileReader("data.txt") 4 )) { 5 6 // Use reader 7 8}
The resource is closed automatically.
Common Java Exceptions
| Exception | Common Cause |
|---|---|
ArithmeticException | Invalid arithmetic operation |
NullPointerException | Using a null reference |
IllegalArgumentException | Invalid method argument |
NumberFormatException | Invalid string-to-number conversion |
IndexOutOfBoundsException | Invalid index |
ArrayIndexOutOfBoundsException | Invalid array index |
ClassCastException | Invalid type cast |
IOException | I/O operation failure |
FileNotFoundException | Requested file cannot be found |
SQLException | Database-related failure |
NumberFormatException Example
Suppose a user enters:
1abc
but the application expects a number.
1public class NumberExample { 2 3 public static void main(String[] args) { 4 5 String input = "abc"; 6 7 try { 8 9 int number = Integer.parseInt(input); 10 11 System.out.println(number); 12 13 } catch (NumberFormatException e) { 14 15 System.out.println( 16 "Please enter a valid number." 17 ); 18 } 19 } 20}
Output:
1Please enter a valid number.
This is a good example of handling an expected input-validation failure.
NullPointerException
Consider:
1String username = null; 2 3System.out.println(username.length());
This results in a NullPointerException.
Instead of routinely catching NullPointerException, it is usually better to prevent invalid null state where possible.
For example:
1String username = null; 2 3if (username != null) { 4 5 System.out.println(username.length()); 6 7} else { 8 9 System.out.println( 10 "Username is not available." 11 ); 12}
The broader principle is:
Prevent programming errors where practical rather than using exception handling as a substitute for correct program logic.
Exception Handling with User Input
A practical example is validating console input.
1import java.util.Scanner; 2 3public class Calculator { 4 5 public static void main(String[] args) { 6 7 Scanner scanner = new Scanner(System.in); 8 9 try { 10 11 System.out.print("Enter first number: "); 12 double first = scanner.nextDouble(); 13 14 System.out.print("Enter second number: "); 15 double second = scanner.nextDouble(); 16 17 if (second == 0) { 18 19 throw new IllegalArgumentException( 20 "The second number cannot be zero." 21 ); 22 } 23 24 double result = first / second; 25 26 System.out.println( 27 "Result: " + result 28 ); 29 30 } catch (java.util.InputMismatchException e) { 31 32 System.out.println( 33 "Please enter valid numeric values." 34 ); 35 36 } catch (IllegalArgumentException e) { 37 38 System.out.println( 39 e.getMessage() 40 ); 41 42 } finally { 43 44 scanner.close(); 45 } 46 } 47}
This example demonstrates:
- User input validation
- Multiple exception types
throwcatchfinally- Resource cleanup
Real-World Banking Example
A better banking application separates business rules from the user interface.
1class InsufficientBalanceException 2 extends RuntimeException { 3 4 public InsufficientBalanceException( 5 String message 6 ) { 7 super(message); 8 } 9} 10 11class BankAccount { 12 13 private double balance; 14 15 public BankAccount(double initialBalance) { 16 17 if (initialBalance < 0) { 18 19 throw new IllegalArgumentException( 20 "Initial balance cannot be negative." 21 ); 22 } 23 24 this.balance = initialBalance; 25 } 26 27 public void deposit(double amount) { 28 29 if (amount <= 0) { 30 31 throw new IllegalArgumentException( 32 "Deposit amount must be positive." 33 ); 34 } 35 36 balance += amount; 37 } 38 39 public void withdraw(double amount) { 40 41 if (amount <= 0) { 42 43 throw new IllegalArgumentException( 44 "Withdrawal amount must be positive." 45 ); 46 } 47 48 if (amount > balance) { 49 50 throw new InsufficientBalanceException( 51 "Insufficient balance." 52 ); 53 } 54 55 balance -= amount; 56 } 57 58 public double getBalance() { 59 60 return balance; 61 } 62}
Application code:
1public class BankApplication { 2 3 public static void main(String[] args) { 4 5 BankAccount account = 6 new BankAccount(5000); 7 8 try { 9 10 account.deposit(1000); 11 account.withdraw(7000); 12 13 } catch (InsufficientBalanceException e) { 14 15 System.out.println( 16 "Transaction failed: " 17 + e.getMessage() 18 ); 19 20 } catch (IllegalArgumentException e) { 21 22 System.out.println( 23 "Invalid transaction: " 24 + e.getMessage() 25 ); 26 } 27 28 System.out.println( 29 "Current balance: ₹" 30 + account.getBalance() 31 ); 32 } 33}
This approach is more maintainable than placing all business logic and exception handling inside main().
Exception Handling in Layers
In real applications, exception handling is often distributed across application layers.
A typical Spring Boot application might look like:
1Controller 2 ↓ 3Service 4 ↓ 5Repository 6 ↓ 7Database
A low-level database exception does not always need to be exposed directly to the user.
Instead, the service layer may translate a low-level failure into a meaningful application-level exception, while the API layer converts that exception into an appropriate HTTP response.
For example:
1Database Exception 2 ↓ 3Service Layer 4 ↓ 5Application Exception 6 ↓ 7Global API Handler 8 ↓ 9HTTP Response
This separation makes applications easier to maintain and debug.
Exception Logging
Avoid silently ignoring exceptions.
Bad:
1try { 2 3 processPayment(); 4 5} catch (Exception e) { 6 7}
The application has lost valuable diagnostic information.
Also avoid relying on:
1System.out.println(e);
for production logging.
A production application should normally use a logging framework and include useful contextual information while avoiding sensitive data.
Conceptually:
1try { 2 3 processPayment(); 4 5} catch (PaymentException e) { 6 7 logger.error( 8 "Payment processing failed", 9 e 10 ); 11 12 throw e; 13}
The exact logging framework depends on the application.
Exception Wrapping
Sometimes a lower-level exception needs to be converted into a higher-level exception.
Example:
1try { 2 3 repository.save(order); 4 5} catch (SQLException e) { 6 7 throw new OrderPersistenceException( 8 "Unable to save order.", 9 e 10 ); 11}
The original exception is preserved as the cause.
A custom exception can support this:
1class OrderPersistenceException 2 extends RuntimeException { 3 4 public OrderPersistenceException( 5 String message, 6 Throwable cause 7 ) { 8 super(message, cause); 9 } 10}
Preserving the cause is important because it keeps the original diagnostic information.
Best Practices
Catch Specific Exceptions
Prefer:
1catch (NumberFormatException e) { 2 // Handle invalid number 3}
over:
1catch (Exception e) { 2 // Handle everything 3}
A broad catch should be used only when there is a clear reason to handle a broad category.
Do Not Swallow Exceptions
Avoid:
1catch (Exception e) { 2}
If an exception cannot be handled meaningfully, propagate it or handle it at an appropriate higher layer.
Use Meaningful Messages
Prefer:
1throw new IllegalArgumentException( 2 "Withdrawal amount must be positive." 3);
over:
1throw new IllegalArgumentException( 2 "Invalid." 3);
Do Not Use Exceptions for Normal Control Flow
Avoid using exceptions for ordinary expected branching.
For example, checking whether a collection is empty is better than deliberately causing an exception and catching it.
Preserve the Original Cause
When wrapping exceptions:
1throw new ServiceException( 2 "Service operation failed.", 3 e 4);
Keep the original exception as the cause.
Use Try-With-Resources
For AutoCloseable resources, prefer:
1try (Resource resource = ...) { 2 3 // Use resource 4 5}
instead of manual cleanup whenever possible.
Do Not Catch Exceptions You Cannot Handle
If a method cannot make a useful decision about an exception, it may be better to let the exception propagate to a layer that can handle it.
Do Not Expose Sensitive Information
Error messages returned to users should not reveal:
- Passwords
- Access tokens
- Database credentials
- Internal server details
- Sensitive personal information
- Full database queries containing secrets
Internal logs can contain appropriate diagnostic information according to your application's security and privacy requirements.
Exception Flow
A simplified exception flow looks like this:
1Program Starts 2 │ 3 ▼ 4 try block 5 │ 6 ▼ 7Exception occurs? 8 │ │ 9 No Yes 10 │ │ 11 │ ▼ 12 │ Matching 13 │ catch block 14 │ │ 15 └──────────┤ 16 ▼ 17 finally 18 │ 19 ▼ 20 Continue / Propagate
If no matching catch block exists, the exception propagates to the calling method.
Practice Project: ATM System
Create an ATM program that validates withdrawals.
1class InsufficientBalanceException 2 extends RuntimeException { 3 4 public InsufficientBalanceException( 5 String message 6 ) { 7 super(message); 8 } 9} 10 11class ATM { 12 13 private double balance; 14 15 public ATM(double balance) { 16 17 if (balance < 0) { 18 19 throw new IllegalArgumentException( 20 "Balance cannot be negative." 21 ); 22 } 23 24 this.balance = balance; 25 } 26 27 public void withdraw(double amount) { 28 29 if (amount <= 0) { 30 31 throw new IllegalArgumentException( 32 "Withdrawal amount must be positive." 33 ); 34 } 35 36 if (amount > balance) { 37 38 throw new InsufficientBalanceException( 39 "Insufficient balance." 40 ); 41 } 42 43 balance -= amount; 44 45 System.out.println( 46 "Withdrawal successful." 47 ); 48 } 49 50 public double getBalance() { 51 52 return balance; 53 } 54} 55 56public class ATMApplication { 57 58 public static void main(String[] args) { 59 60 ATM atm = new ATM(5000); 61 62 try { 63 64 atm.withdraw(7000); 65 66 } catch (InsufficientBalanceException e) { 67 68 System.out.println( 69 "Transaction failed: " 70 + e.getMessage() 71 ); 72 73 } catch (IllegalArgumentException e) { 74 75 System.out.println( 76 "Invalid request: " 77 + e.getMessage() 78 ); 79 } 80 81 System.out.println( 82 "Available balance: ₹" 83 + atm.getBalance() 84 ); 85 } 86}
Sample output:
1Transaction failed: Insufficient balance. 2Available balance: ₹5000.0
Practice Project: File Reader
Create a file-reading application using try-with-resources.
1import java.io.BufferedReader; 2import java.io.IOException; 3import java.nio.file.Files; 4import java.nio.file.Path; 5 6public class FileReaderApplication { 7 8 public static void main(String[] args) { 9 10 Path file = Path.of("data.txt"); 11 12 try (BufferedReader reader = 13 Files.newBufferedReader(file)) { 14 15 String line; 16 17 while ((line = reader.readLine()) != null) { 18 19 System.out.println(line); 20 } 21 22 } catch (IOException e) { 23 24 System.err.println( 25 "Unable to read file." 26 ); 27 28 System.err.println( 29 "Reason: " + e.getMessage() 30 ); 31 } 32 } 33}
This is preferable to manually managing a FileReader because try-with-resources automatically closes the reader.
Practice Project: Login Validation
Create a custom exception for invalid login credentials.
1class InvalidLoginException 2 extends RuntimeException { 3 4 public InvalidLoginException( 5 String message 6 ) { 7 super(message); 8 } 9} 10 11class LoginService { 12 13 public void login( 14 String username, 15 String password 16 ) { 17 18 if (!"admin".equals(username) 19 || !"java123".equals(password)) { 20 21 throw new InvalidLoginException( 22 "Invalid username or password." 23 ); 24 } 25 26 System.out.println( 27 "Login successful." 28 ); 29 } 30} 31 32public class LoginApplication { 33 34 public static void main(String[] args) { 35 36 LoginService service = 37 new LoginService(); 38 39 try { 40 41 service.login( 42 "admin", 43 "wrong-password" 44 ); 45 46 } catch (InvalidLoginException e) { 47 48 System.out.println( 49 e.getMessage() 50 ); 51 } 52 } 53}
In a real authentication system, passwords should never be hard-coded or compared as plain text like this. The example is only demonstrating exception handling and validation flow.
Summary
Java exception handling provides a structured way to deal with exceptional conditions without allowing every error to unexpectedly terminate the application.
In this tutorial, you learned:
- What an exception is
- The
Throwablehierarchy - The difference between
ErrorandException - Checked and unchecked exceptions
- How
tryworks - How
catchhandles exceptions - Multiple catch blocks
- Multi-catch
- The purpose of
finally - How to use
throw - How to use
throws - Exception propagation
- Custom exceptions
- Try-with-resources
- Exception logging
- Exception wrapping
- Practical exception-handling patterns
- Best practices for production applications
The most important principle is that exception handling should make an application more reliable and maintainable, not simply hide errors.
Handle an exception where you can make a meaningful decision, preserve useful diagnostic information, clean up resources correctly, and expose only appropriate information to users.
Practice Exercises
Exercise 1: Student Registration
Create an InvalidMarksException.
Throw it when marks are:
1marks < 0
or:
1marks > 100
Validate several students and handle invalid marks gracefully.
Exercise 2: Calculator
Create a calculator that handles:
- Division by zero
- Invalid numeric input
- Invalid operators
- Negative values where your business rules prohibit them
Use specific exception types instead of catching every error with Exception.
Exercise 3: File Reader
Create a program that:
- Reads a text file
- Prints each line
- Handles
IOException - Uses try-with-resources
- Displays a useful error message if the file cannot be read
Exercise 4: Online Shopping
Create:
1OutOfStockException
Throw it when a customer tries to purchase more products than are available.
Exercise 5: Banking Application
Build a banking application that:
- Creates a bank account
- Validates deposits
- Validates withdrawals
- Throws
InsufficientBalanceException - Handles invalid amounts
- Preserves meaningful error messages
- Separates business logic from the application entry point
Exercise 6: REST API Exception Design
Design a small Spring Boot REST API where:
1UserNotFoundException 2InvalidRequestException 3DuplicateUserException
are converted into appropriate HTTP responses using centralized exception handling.
This exercise will prepare you for advanced Java topics such as Collections Framework, Generics, File Handling, Multithreading, Lambda Expressions, Streams, JDBC, and Spring Boot exception handling.