• Home
  • LLMs
  • Docker
  • Kubernetes
  • Java
  • All
  • About
Java | Exceptions
  1. Exceptions
  2. Exception Handling
  3. The try-catch-finally Clauses
  4. Throwing an Exception: throw
  5. Declaring an Exception: throws
  6. Creating Custom Exception
  7. Try-with-resources Statement

  1. Exceptions
    Exceptions in Java are grouped into three main categories:
    • Errors (class: Error) - Serious problems that applications should not try to catch.
    • Unchecked exceptions (class: RuntimeException) - Exceptions that are not checked at compile time.
    • Checked exceptions (class: Exception, excluding RuntimeException) - Exceptions that must be handled or declared.

                  java.lang.Object
                         |
                         |
                  java.lang.Throwable
                         |
          _______________|_______________
         |                               |
        \_/                             \_/
    java.lang.Error                java.lang.Exception
                                         |
                     ____________________|____________________
                    |                                         |
                   \_/                                       \_/
         java.lang.RuntimeException               [Other subclasses of Exception]
    Checked vs. Unchecked Exceptions:
    • Checked exceptions are compile-time enforced exceptions that must be either handled with a try-catch block or declared in the method signature using the throws keyword. These include all exceptions that extend Exception but not RuntimeException. Examples: IOException, SQLException, ClassNotFoundException.

    • Unchecked exceptions are runtime exceptions that do not need to be explicitly handled or declared. These include:
      • All subclasses of RuntimeException (e.g., NullPointerException, ArithmeticException)
      • All subclasses of Error (e.g., OutOfMemoryError, StackOverflowError)
  2. Exception Handling
    To handle an exception, we use a try-catch block:
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("main: start");
            try {
                int a = 10 / 0;  // This will throw ArithmeticException
                System.out.println("Result: " + a);  // This line won't execute
            } catch (ArithmeticException e) {
                System.out.println("Cannot divide by zero!");
                e.printStackTrace();
            }
            System.out.println("main: end");
        }
    }
    Output:
    main: start
    Cannot divide by zero!
    java.lang.ArithmeticException: / by zero
        at com.mtitek.exceptions.MyTest1.main(MyTest1.java:7)
    main: end
    Note: ArithmeticException is a subclass of RuntimeException (unchecked exception), so the compiler does not require this exception to be handled. However, handling it prevents program termination:
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("main: start");
    
            int a = 10 / 0;  // Unhandled exception will terminate the program
    
            System.out.println("main: end");  // This line will never execute
        }
    }
    Output:
    main: start
    Exception in thread "main" java.lang.ArithmeticException: / by zero
        at com.mtitek.exceptions.MyTest1.main(MyTest1.java:7)
    When the JVM encounters an unhandled exception, the program terminates abruptly.

    Exception Propagation: Exception handling prevents exceptions from propagating up the call stack. Without handling, exceptions propagate through method calls until they reach the main method. If not caught there, the program terminates.

    Note: While all exceptions extending Throwable can be caught, it's recommended to handle only Exception and its subclasses. Error types typically indicate serious JVM problems that applications cannot recover from (e.g., OutOfMemoryError, StackOverflowError).
  3. The try-catch-finally Clauses
    The try, catch, and finally clauses work together for comprehensive exception handling:
    • try: Defines a block of code that might throw an exception.

    • catch: Handles specific exceptions thrown by the try block.

    • finally: Contains cleanup code that executes regardless of whether an exception occurs.

    Usage Rules:
    • A try block must be followed by at least one catch block or one finally block. Otherwise, you'll get a compilation error:
      try {
          int a = 10 / 0;
      } // Compiler error: "Syntax error, insert 'Finally' to complete BlockStatements"
    • Multiple catch blocks can handle different exception types, but they must be ordered from most specific to most general:
      try {
          // Code that might throw exceptions
      } catch (ArithmeticException e) {
          // Handle arithmetic exceptions specifically
          System.out.println("Arithmetic error: " + e.getMessage());
      } catch (RuntimeException e) {
          // Handle other runtime exceptions
          System.out.println("Runtime error: " + e.getMessage());
      } catch (Exception e) {
          // Handle any other exceptions
          System.out.println("General error: " + e.getMessage());
      }
    • Each catch block must handle a distinct exception type. Duplicate exception types result in compilation errors:
      try {
          // ...
      } catch (Exception e) {
          // ...
      } catch (Exception e) { // Compiler error: Unreachable catch block
          // ...
      }
    • Catch blocks must be ordered from specific to general exceptions. A more general exception type cannot appear before a more specific one:
      try {
          // ...
      } catch (Exception e) { // Catches all exceptions
          // ...
      } catch (ArithmeticException e) { // Compiler error: Unreachable - already caught by Exception
          // ...
      }
    • Once a catch block executes, remaining catch blocks are skipped. If the catch block itself throws an exception, it must be handled by an outer try-catch:
      public static void main(String[] args) {
          System.out.println("main: start");
      
          try { // outer try
              try { // inner try
                  int a = 10 / 0; // First exception thrown here
              } catch (ArithmeticException e) {
                  System.out.println("inner try: caught ArithmeticException");
                  int b = 10 / 0; // Second exception thrown here
              } catch (Exception e) {
                  System.out.println("inner try: catch Exception"); // This block is skipped
              }
          } catch (Exception e) {
              System.out.println("outer try: caught Exception"); // Second exception caught here
          }
      
          System.out.println("main: end");
      }
      Output:
      main: start
      inner try: caught ArithmeticException
      outer try: caught Exception
      main: end
    • A finally block can be used without catch blocks for cleanup operations. This is useful when you want to perform cleanup but let the exception propagate:
      public static void main(String[] args) {
          System.out.println("main: start");
      
          try { // outer try
              try { // inner try
                  int a = 10 / 0; // Exception thrown here
              }
              finally {
                  System.out.println("inner try: finally - cleanup performed");
              }
          } catch (Exception e) {
              System.out.println("outer try: caught Exception");
          }
      
          System.out.println("main: end");
      }
      Output:
      main: start
      inner try: finally - cleanup performed
      outer try: caught Exception
      main: end
    • The finally block always executes, regardless of whether an exception occurs or is caught. It executes even when a catch block throws another exception:
      public static void main(String[] args) {
          System.out.println("main: start");
      
          try { // outer try
              try { // inner try
                  int a = 10 / 0; // First exception thrown here
              } catch (ArithmeticException e) {
                  System.out.println("inner try: caught ArithmeticException");
                  int b = 10 / 0; // Second exception thrown here
              } finally {
                  System.out.println("inner try: finally - always executes");
              }
          } catch (Exception e) {
              System.out.println("outer try: caught Exception");
          }
      
          System.out.println("main: end");
      }
      Output:
      main: start
      inner try: caught ArithmeticException
      inner try: finally - always executes
      outer try: caught Exception
      main: end

    Notes:
    • Use finally blocks for resource cleanup (closing files, database connections, etc.).
    • Consider using try-with-resources for automatic resource management.
    • Avoid putting return statements in finally blocks as they can mask exceptions.
    • The finally block executes even if there's a return statement in the try or catch block.
  4. Throwing an Exception: throw
    It is possible to throw an exception in code, for example, when an invalid situation is encountered during execution.
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("main: start");
    
            try {
                int v1 = 10;
                int v2 = 0;
    
                if (v2 == 0) {
                    throw new ArithmeticException("Warning, integer division by zero!");
                } else {
                    int v = v1 / v2;
                }
            } catch (ArithmeticException e) {
                e.printStackTrace();
            }
    
            System.out.println("main: end");
        }
    }
    Output:
    main: start
    java.lang.ArithmeticException: Warning, integer division by zero!
        at test.MyTest1.main(MyTest1.java:12)
    main: end
    This example demonstrates how the throw keyword is used to explicitly throw an exception when a specific condition is met. Note that ArithmeticException is a runtime (unchecked) exception, so it doesn't need to be declared in the method signature.
  5. Declaring an Exception: throws
    A method that throws checked exceptions (or calls other methods that might throw checked exceptions) must declare those exceptions using the throws keyword. This allows calling methods to know about the exceptions that may be thrown, and either handle them or declare them as well.

    The compiler only enforces declaration and handling for checked exceptions (those that extend Exception but not RuntimeException). Unchecked exceptions (those extending RuntimeException) and errors (extending Error) can be thrown without declaration. In Java, you must either handle or declare a checked exception, otherwise the compiler will generate a compilation error.

    To declare a single exception: throws Exception
    To declare multiple exceptions: throws IOException, SQLException
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("main: start");
    
            try {
                int v1 = divide(10, 0);
                System.out.println("Result: " + v1);
            } catch (Exception e) {
                System.err.println("Caught exception: " + e.getMessage());
                e.printStackTrace();
            }
    
            System.out.println("main: end");
        }
    
        private static int divide(int p1, int p2) throws Exception {
            if (p2 == 0) {
                throw new Exception("Division by zero is not allowed!");
            }
            return p1 / p2;
        }
    }
    Output:
    main: start
    Caught exception: Division by zero is not allowed!
    java.lang.Exception: Division by zero is not allowed!
        at test.MyTest1.divide(MyTest1.java:20)
        at test.MyTest1.main(MyTest1.java:8)
    main: end
    A method must either handle or declare the checked exceptions thrown by the methods it calls.

    In the previous example, the main method handles the exception thrown by the divide method. Alternatively, main could declare the exception instead of handling it:
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) throws Exception {
            System.out.println("main: start");
    
            int v1 = divide(10, 0);
            System.out.println("Result: " + v1);
    
            System.out.println("main: end");
        }
    
        private static int divide(int p1, int p2) throws Exception {
            if (p2 == 0) {
                throw new Exception("Division by zero is not allowed!");
            }
            return p1 / p2;
        }
    }
    Output:
    main: start
    Exception in thread "main" java.lang.Exception: Division by zero is not allowed!
        at test.MyTest1.divide(MyTest1.java:15)
        at test.MyTest1.main(MyTest1.java:7)
    Note that the program terminates abruptly because the exception was not handled. Since the main method is at the top of the call stack, the JVM stops propagating the exception and terminates the program with an error message.
  6. Creating Custom Exceptions
    Exceptions are classes that inherit from the Throwable hierarchy. Creating custom exceptions is useful when you want to handle specific types of errors and distinguish them from other exceptions.

    Custom exception classes must extend one of the following:
    • Exception - for checked exceptions that must be handled or declared.
    • RuntimeException - for unchecked exceptions.
    • Error - for system-level errors (rarely used).

    Note: Extend Exception for recoverable conditions that calling code should handle, and RuntimeException for programming errors.
    public class BigNumberException extends Exception {
        public BigNumberException() {
            super("Number is too large for this operation");
        }
    
        public BigNumberException(String message) {
            super(message);
        }
    }
    public class SmallNumberException extends Exception {
        public SmallNumberException() {
            super("Number is too small for this operation");
        }
    
        public SmallNumberException(String message) {
            super(message);
        }
    }
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("main: start");
    
            // Test case 1: Division by zero (unchecked exception)
            try {
                int v1 = divide(10, 0);
                System.out.println("Result: " + v1);
            } catch (BigNumberException | SmallNumberException e) {
                System.err.println("Custom exception: " + e.getMessage());
            } catch (ArithmeticException e) {
                System.err.println("Arithmetic error: " + e.getMessage());
            }
    
            // Test case 2: Large number (checked exception)
            try {
                int v1 = divide(100, 2);
                System.out.println("Result: " + v1);
            } catch (BigNumberException | SmallNumberException e) {
                System.err.println("Custom exception: " + e.getMessage());
            } catch (ArithmeticException e) {
                System.err.println("Arithmetic error: " + e.getMessage());
            }
    
            // Test case 3: Small number (checked exception)
            try {
                int v1 = divide(-10, 2);
                System.out.println("Result: " + v1);
            } catch (BigNumberException | SmallNumberException e) {
                System.err.println("Custom exception: " + e.getMessage());
            } catch (ArithmeticException e) {
                System.err.println("Arithmetic error: " + e.getMessage());
            }
    
            System.out.println("main: end");
        }
    
        private static int divide(int p1, int p2) throws SmallNumberException, BigNumberException {
            if (p2 == 0) {
                // ArithmeticException is unchecked, so no need to declare it
                throw new ArithmeticException("Division by zero is not allowed!");
            } else if (p1 < 0) {
                throw new SmallNumberException("Negative numbers not supported: " + p1);
            } else if (p2 > 50) {
                throw new BigNumberException("Number too large for operation: " + p1);
            } else {
                return p1 / p2;
            }
        }
    }
    Output:
    main: start
    Arithmetic error: Division by zero is not allowed!
    Result: 50
    Custom exception: Negative numbers not supported: -10
    main: end
  7. Try-with-resources Statement
    The try-with-resources statement is a special form of try statement that automatically manages resources that implement the AutoCloseable interface. It ensures that resources are properly closed even if an exception occurs, eliminating the need for explicit finally blocks in most cases.

    Resources declared in the try-with-resources statement are automatically closed at the end of the statement, whether the code executes normally or an exception is thrown. This helps prevent resource leaks and makes code more readable and maintainable.

    Syntax:
    try (ResourceType resource = new ResourceType()) {
        // do something here
    } catch (ExceptionType e) {
        // handle exception
    }
    You can declare multiple resources in a single try-with-resources statement by separating them with semicolons. Resources are closed in reverse order of their declaration.

    Syntax:
    try (ResourceType resource = new ResourceType();
            AnotherResourceType anotherResource = new AnotherResourceType()) {
        // do something here
    } catch (ExceptionType e) {
        // handle exception
    }
    Note: Always use try-with-resources for AutoCloseable resources instead of manual resource management.

    Example:
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            System.out.println("Using try-with-resources:");
    
            try (MyAutoCloseableResource resource = new MyAutoCloseableResource()) {
                resource.doSomething();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            System.out.println("main: end");
        }
    }
    class MyAutoCloseableResource implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("Closing resources ....");
        }
    
        public void doSomething() {
            System.out.println("Doing something ...");
        }
    }
    Output:
    Using try-with-resources:
    Doing something ...
    Closing resources ....
    main: end
    
    Suppressed Exceptions:
    If an exception occurs both in the try block and in the close() method, the exception from the try block is thrown, and the exception from close() is added as a suppressed exception.
    package com.mtitek.exceptions;
    
    public class MyTest1 {
        public static void main(String[] args) {
            try (MyAnotherAutoCloseableResource resource = new MyAnotherAutoCloseableResource()) {
                resource.doSomething();
            } catch (Exception e) {
                System.err.println("Main exception: " + e.getMessage());
    
                // Check for suppressed exceptions
                Throwable[] suppressed = e.getSuppressed();
                for (Throwable t : suppressed) {
                    System.err.println("Suppressed: " + t.getMessage());
                }
            }
        }
    }
    class MyAnotherAutoCloseableResource implements AutoCloseable {
        @Override
        public void close() throws Exception {
            throw new Exception("Error closing resource ...");
        }
    
        public void doSomething() throws Exception {
            throw new Exception("Error during doing something ...");
        }
    }
    Output:
    Main exception: Error during doing something ...
    Suppressed: Error closing resource ...
© 2025  mtitek