Java Exception Handling: Dealing with Things That Go Wrong

Java Exception Handling: Dealing with Things That Go Wrong

Here’s the thing about programming - stuff breaks. Files don’t exist, networks fail, users type nonsense, and hard drives fill up. The difference between a beginner and an experienced developer isn’t that experienced developers write code that never breaks - it’s that they know how to handle it gracefully when it does.

Exception handling in Java isn’t just about preventing crashes. It’s about creating robust applications that can recover from problems, give users helpful feedback, and keep running when things go sideways. Trust me, six months from now, you’ll be amazed at how much more professional your code feels when you handle errors properly.

Why Exception Handling Matters for Your Career

Before we dive into the syntax, let me tell you why this stuff is huge for career advancement. Every company has systems that deal with unreliable external resources - files, databases, web services, user input. When you can write code that fails gracefully and provides useful error messages, people notice. It’s the difference between code that works in demos and code that works in production.

What Are Exceptions Anyway?

An exception is Java’s way of saying “something unexpected happened.” Instead of crashing your entire program, Java creates an exception object that contains information about what went wrong, then gives you a chance to handle it.

Think of exceptions like smoke alarms in your house. When there’s a problem, the alarm goes off, but it doesn’t burn down your house - it gives you a chance to deal with the situation.

 1public class ExceptionBasics {
 2    public static void main(String[] args) {
 3        // This will throw an exception
 4        try {
 5            int result = 10 / 0;  // Division by zero!
 6            System.out.println("Result: " + result);
 7        } catch (ArithmeticException e) {
 8            System.out.println("Oops! Can't divide by zero: " + e.getMessage());
 9        }
10        
11        System.out.println("Program continues running...");
12    }
13}

Without the try-catch block, this program would crash. With it, we handle the problem and keep going.

The Exception Hierarchy

Java organizes exceptions in a hierarchy that actually makes sense once you understand it:

 1public class ExceptionHierarchy {
 2    public static void demonstrateExceptionTypes() {
 3        System.out.println("=== Exception Type Examples ===");
 4        
 5        // RuntimeException (unchecked) - you don't have to catch these
 6        try {
 7            String text = null;
 8            int length = text.length();  // NullPointerException
 9        } catch (NullPointerException e) {
10            System.out.println("Null pointer: " + e.getMessage());
11        }
12        
13        try {
14            int[] numbers = {1, 2, 3};
15            int value = numbers[5];  // ArrayIndexOutOfBoundsException
16        } catch (ArrayIndexOutOfBoundsException e) {
17            System.out.println("Array index problem: " + e.getMessage());
18        }
19        
20        try {
21            String notANumber = "hello";
22            int number = Integer.parseInt(notANumber);  // NumberFormatException
23        } catch (NumberFormatException e) {
24            System.out.println("Can't parse number: " + e.getMessage());
25        }
26        
27        // You can catch multiple specific exceptions
28        try {
29            // Code that might throw different exceptions
30            processUserInput("invalid input");
31        } catch (NumberFormatException e) {
32            System.out.println("Number format error: " + e.getMessage());
33        } catch (IllegalArgumentException e) {
34            System.out.println("Invalid argument: " + e.getMessage());
35        } catch (Exception e) {
36            System.out.println("Something else went wrong: " + e.getMessage());
37        }
38    }
39    
40    private static void processUserInput(String input) {
41        if (input == null || input.trim().isEmpty()) {
42            throw new IllegalArgumentException("Input cannot be empty");
43        }
44        
45        // Try to parse as number
46        Integer.parseInt(input);  // This will throw NumberFormatException
47    }
48}

File I/O Exception Handling - The Real World

File operations are where you’ll encounter exceptions most often. Let’s start with the basic scenario every developer faces: reading a file that might not exist.

Basic File Reading with Exception Handling

  1import java.io.*;
  2import java.util.ArrayList;
  3import java.util.List;
  4
  5public class FileReadingWithExceptions {
  6    
  7    public static List<String> readFileBasic(String filename) {
  8        System.out.println("=== Basic File Reading ===");
  9        List<String> lines = new ArrayList<>();
 10        
 11        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
 12            String line;
 13            while ((line = reader.readLine()) != null) {
 14                lines.add(line);
 15            }
 16            System.out.println("Successfully read " + lines.size() + " lines from " + filename);
 17            
 18        } catch (FileNotFoundException e) {
 19            System.out.println("File not found: " + filename);
 20            System.out.println("Make sure the file exists and the path is correct.");
 21            
 22        } catch (IOException e) {
 23            System.out.println("Error reading file: " + e.getMessage());
 24            System.out.println("This could be a permission issue or the file is locked.");
 25            
 26        } catch (SecurityException e) {
 27            System.out.println("Security error: " + e.getMessage());
 28            System.out.println("The application doesn't have permission to read this file.");
 29        }
 30        
 31        return lines;
 32    }
 33    
 34    public static List<String> readFileRobust(String filename) {
 35        System.out.println("\n=== Robust File Reading ===");
 36        List<String> lines = new ArrayList<>();
 37        
 38        // Pre-flight checks
 39        File file = new File(filename);
 40        
 41        if (!file.exists()) {
 42            System.out.println("File doesn't exist: " + filename);
 43            return lines;  // Return empty list instead of crashing
 44        }
 45        
 46        if (!file.canRead()) {
 47            System.out.println("Cannot read file: " + filename);
 48            return lines;
 49        }
 50        
 51        if (file.isDirectory()) {
 52            System.out.println("Path is a directory, not a file: " + filename);
 53            return lines;
 54        }
 55        
 56        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
 57            String line;
 58            int lineNumber = 0;
 59            
 60            while ((line = reader.readLine()) != null) {
 61                lineNumber++;
 62                lines.add(line);
 63                
 64                // Handle very large files gracefully
 65                if (lineNumber > 100000) {
 66                    System.out.println("Warning: File is very large, stopping at 100,000 lines");
 67                    break;
 68                }
 69            }
 70            
 71            System.out.println("Successfully read " + lines.size() + " lines from " + filename);
 72            
 73        } catch (IOException e) {
 74            System.out.println("Error reading file: " + e.getMessage());
 75            // Log the error but don't crash - return what we have so far
 76        }
 77        
 78        return lines;
 79    }
 80    
 81    public static void demonstrateFileReading() {
 82        // Test with non-existent file
 83        readFileBasic("does_not_exist.txt");
 84        
 85        // Create a test file and read it
 86        createTestFile("test.txt");
 87        readFileRobust("test.txt");
 88    }
 89    
 90    private static void createTestFile(String filename) {
 91        try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
 92            writer.println("Line 1: This is a test file");
 93            writer.println("Line 2: Created for exception handling demo");
 94            writer.println("Line 3: Each line has different content");
 95            writer.println("Line 4: To show how file reading works");
 96            writer.println("Line 5: With proper exception handling");
 97            System.out.println("Created test file: " + filename);
 98        } catch (IOException e) {
 99            System.out.println("Error creating test file: " + e.getMessage());
100        }
101    }
102}

File Writing with Exception Handling

Writing files brings its own set of challenges - disk space, permissions, concurrent access. Here’s how to handle them:

  1import java.io.*;
  2import java.util.List;
  3import java.util.Arrays;
  4
  5public class FileWritingWithExceptions {
  6    
  7    public static boolean writeFileBasic(String filename, List<String> data) {
  8        System.out.println("=== Basic File Writing ===");
  9        
 10        try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
 11            for (String line : data) {
 12                writer.println(line);
 13            }
 14            System.out.println("Successfully wrote " + data.size() + " lines to " + filename);
 15            return true;
 16            
 17        } catch (IOException e) {
 18            System.out.println("Error writing file: " + e.getMessage());
 19            return false;
 20        }
 21    }
 22    
 23    public static boolean writeFileSafe(String filename, List<String> data) {
 24        System.out.println("\n=== Safe File Writing ===");
 25        
 26        // Create parent directories if they don't exist
 27        File file = new File(filename);
 28        File parentDir = file.getParentFile();
 29        
 30        if (parentDir != null && !parentDir.exists()) {
 31            try {
 32                if (parentDir.mkdirs()) {
 33                    System.out.println("Created directories: " + parentDir.getPath());
 34                } else {
 35                    System.out.println("Failed to create directories: " + parentDir.getPath());
 36                    return false;
 37                }
 38            } catch (SecurityException e) {
 39                System.out.println("Permission denied creating directories: " + e.getMessage());
 40                return false;
 41            }
 42        }
 43        
 44        // Check available space (simplified check)
 45        long freeSpace = file.getParentFile().getFreeSpace();
 46        long estimatedSize = data.size() * 50; // Rough estimate
 47        
 48        if (freeSpace < estimatedSize) {
 49            System.out.println("Warning: Low disk space. Available: " + freeSpace + " bytes");
 50        }
 51        
 52        // Write to temporary file first, then rename (atomic operation)
 53        String tempFilename = filename + ".tmp";
 54        
 55        try (PrintWriter writer = new PrintWriter(new FileWriter(tempFilename))) {
 56            for (int i = 0; i < data.size(); i++) {
 57                writer.println(data.get(i));
 58                
 59                // Check for errors during writing
 60                if (writer.checkError()) {
 61                    throw new IOException("Error occurred while writing line " + (i + 1));
 62                }
 63            }
 64            
 65            // If we get here, writing was successful
 66            writer.close();
 67            
 68            // Atomic rename
 69            File tempFile = new File(tempFilename);
 70            File finalFile = new File(filename);
 71            
 72            if (tempFile.renameTo(finalFile)) {
 73                System.out.println("Successfully wrote " + data.size() + " lines to " + filename);
 74                return true;
 75            } else {
 76                System.out.println("Error: Could not rename temporary file");
 77                tempFile.delete();  // Clean up
 78                return false;
 79            }
 80            
 81        } catch (IOException e) {
 82            System.out.println("Error writing file: " + e.getMessage());
 83            
 84            // Clean up temporary file
 85            File tempFile = new File(tempFilename);
 86            if (tempFile.exists()) {
 87                tempFile.delete();
 88            }
 89            
 90            return false;
 91        }
 92    }
 93    
 94    public static boolean appendToFile(String filename, String content) {
 95        System.out.println("\n=== Appending to File ===");
 96        
 97        try (PrintWriter writer = new PrintWriter(new FileWriter(filename, true))) {
 98            writer.println(content);
 99            System.out.println("Successfully appended content to " + filename);
100            return true;
101            
102        } catch (IOException e) {
103            System.out.println("Error appending to file: " + e.getMessage());
104            return false;
105        }
106    }
107    
108    public static void demonstrateFileWriting() {
109        List<String> testData = Arrays.asList(
110            "First line of data",
111            "Second line with more content",
112            "Third line: numbers 123, 456, 789",
113            "Fourth line: special chars !@#$%^&*()",
114            "Fifth line: final content"
115        );
116        
117        // Test basic writing
118        writeFileBasic("output1.txt", testData);
119        
120        // Test safe writing with directory creation
121        writeFileSafe("subdirectory/output2.txt", testData);
122        
123        // Test appending
124        appendToFile("output1.txt", "This line was appended later");
125    }
126}

Advanced Exception Handling Patterns

Here are some patterns you’ll use in real applications:

  1import java.io.*;
  2import java.util.List;
  3import java.util.ArrayList;
  4
  5public class AdvancedExceptionHandling {
  6    
  7    // Custom exception for our application
  8    public static class FileProcessingException extends Exception {
  9        private final String filename;
 10        private final int lineNumber;
 11        
 12        public FileProcessingException(String message, String filename, int lineNumber, Throwable cause) {
 13            super(message, cause);
 14            this.filename = filename;
 15            this.lineNumber = lineNumber;
 16        }
 17        
 18        public String getFilename() { return filename; }
 19        public int getLineNumber() { return lineNumber; }
 20        
 21        @Override
 22        public String toString() {
 23            return String.format("FileProcessingException: %s (file: %s, line: %d)", 
 24                               getMessage(), filename, lineNumber);
 25        }
 26    }
 27    
 28    public static List<Integer> parseNumbersFromFile(String filename) throws FileProcessingException {
 29        List<Integer> numbers = new ArrayList<>();
 30        
 31        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
 32            String line;
 33            int lineNumber = 0;
 34            
 35            while ((line = reader.readLine()) != null) {
 36                lineNumber++;
 37                
 38                // Skip empty lines and comments
 39                if (line.trim().isEmpty() || line.trim().startsWith("#")) {
 40                    continue;
 41                }
 42                
 43                try {
 44                    int number = Integer.parseInt(line.trim());
 45                    numbers.add(number);
 46                } catch (NumberFormatException e) {
 47                    // Wrap the original exception with context
 48                    throw new FileProcessingException(
 49                        "Invalid number format: '" + line.trim() + "'", 
 50                        filename, 
 51                        lineNumber, 
 52                        e
 53                    );
 54                }
 55            }
 56            
 57        } catch (FileNotFoundException e) {
 58            throw new FileProcessingException("File not found", filename, 0, e);
 59        } catch (IOException e) {
 60            throw new FileProcessingException("Error reading file", filename, 0, e);
 61        }
 62        
 63        return numbers;
 64    }
 65    
 66    public static void processFileWithRetry(String filename, int maxRetries) {
 67        System.out.println("=== Processing File with Retry Logic ===");
 68        
 69        for (int attempt = 1; attempt <= maxRetries; attempt++) {
 70            try {
 71                List<Integer> numbers = parseNumbersFromFile(filename);
 72                System.out.println("Successfully parsed " + numbers.size() + " numbers");
 73                System.out.println("Numbers: " + numbers);
 74                return;  // Success! Exit the retry loop
 75                
 76            } catch (FileProcessingException e) {
 77                System.out.println("Attempt " + attempt + " failed: " + e.getMessage());
 78                
 79                if (attempt == maxRetries) {
 80                    System.out.println("All retry attempts exhausted. Details:");
 81                    System.out.println("  File: " + e.getFilename());
 82                    if (e.getLineNumber() > 0) {
 83                        System.out.println("  Line: " + e.getLineNumber());
 84                    }
 85                    System.out.println("  Root cause: " + e.getCause().getClass().getSimpleName());
 86                } else {
 87                    System.out.println("Retrying in 1 second...");
 88                    try {
 89                        Thread.sleep(1000);  // Wait before retry
 90                    } catch (InterruptedException ie) {
 91                        Thread.currentThread().interrupt();
 92                        System.out.println("Interrupted during retry wait");
 93                        return;
 94                    }
 95                }
 96            }
 97        }
 98    }
 99    
100    public static void demonstrateMultipleFileOperations() {
101        System.out.println("\n=== Multiple File Operations ===");
102        
103        String[] filenames = {"numbers1.txt", "numbers2.txt", "numbers3.txt"};
104        List<Integer> allNumbers = new ArrayList<>();
105        List<String> failedFiles = new ArrayList<>();
106        
107        for (String filename : filenames) {
108            try {
109                // Create test file
110                createNumberFile(filename);
111                
112                List<Integer> numbers = parseNumbersFromFile(filename);
113                allNumbers.addAll(numbers);
114                System.out.println("Processed " + filename + ": " + numbers.size() + " numbers");
115                
116            } catch (FileProcessingException e) {
117                System.out.println("Failed to process " + filename + ": " + e.getMessage());
118                failedFiles.add(filename);
119            }
120        }
121        
122        System.out.println("\nSummary:");
123        System.out.println("Total numbers processed: " + allNumbers.size());
124        System.out.println("Failed files: " + failedFiles.size());
125        
126        if (!failedFiles.isEmpty()) {
127            System.out.println("Files that failed: " + failedFiles);
128        }
129    }
130    
131    private static void createNumberFile(String filename) {
132        try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
133            // Mix of valid numbers, comments, and one invalid entry for demo
134            writer.println("# This is a comment");
135            writer.println("42");
136            writer.println("123");
137            writer.println("");  // Empty line
138            writer.println("456");
139            
140            // Add an invalid entry in the third file
141            if (filename.equals("numbers3.txt")) {
142                writer.println("not_a_number");  // This will cause an exception
143            }
144            
145            writer.println("789");
146        } catch (IOException e) {
147            System.out.println("Error creating test file: " + e.getMessage());
148        }
149    }
150    
151    public static void demonstrateAdvancedHandling() {
152        // Test with valid file
153        createNumberFile("valid_numbers.txt");
154        processFileWithRetry("valid_numbers.txt", 3);
155        
156        // Test with file that has invalid content
157        processFileWithRetry("numbers3.txt", 3);
158        
159        // Test multiple file processing
160        demonstrateMultipleFileOperations();
161    }
162}

Exception Handling Best Practices

Here are the patterns that separate professional code from amateur code:

Do This (Good Practices)

 1public class ExceptionBestPractices {
 2    
 3    // Good: Specific exception types
 4    public void processFile(String filename) throws FileNotFoundException, IOException {
 5        if (filename == null || filename.trim().isEmpty()) {
 6            throw new IllegalArgumentException("Filename cannot be null or empty");
 7        }
 8        
 9        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
10            // Process file
11        }
12        // File automatically closed by try-with-resources
13    }
14    
15    // Good: Informative error messages
16    public int parseInteger(String input, String fieldName) throws NumberFormatException {
17        if (input == null) {
18            throw new NumberFormatException("Cannot parse null value for field: " + fieldName);
19        }
20        
21        try {
22            return Integer.parseInt(input.trim());
23        } catch (NumberFormatException e) {
24            throw new NumberFormatException(
25                String.format("Invalid integer value '%s' for field '%s'", input, fieldName)
26            );
27        }
28    }
29    
30    // Good: Logging exceptions properly
31    public boolean saveUserData(String filename, String data) {
32        try {
33            Files.write(Paths.get(filename), data.getBytes());
34            return true;
35        } catch (IOException e) {
36            // Log the full exception details
37            System.err.println("Failed to save user data to " + filename);
38            System.err.println("Error: " + e.getMessage());
39            e.printStackTrace();  // In real code, use a logging framework
40            return false;
41        }
42    }
43    
44    // Good: Cleanup in finally or try-with-resources
45    public void processFileWithCleanup(String filename) throws IOException {
46        FileInputStream fis = null;
47        try {
48            fis = new FileInputStream(filename);
49            // Process file
50        } finally {
51            if (fis != null) {
52                try {
53                    fis.close();
54                } catch (IOException e) {
55                    System.err.println("Error closing file: " + e.getMessage());
56                }
57            }
58        }
59    }
60}

Don’t Do This (Anti-Patterns)

 1public class ExceptionAntiPatterns {
 2    
 3    // Bad: Swallowing exceptions
 4    public void badExample1(String filename) {
 5        try {
 6            // Some file operation
 7            Files.delete(Paths.get(filename));
 8        } catch (IOException e) {
 9            // Silent failure - you'll never know what went wrong!
10        }
11    }
12    
13    // Bad: Catching Exception (too broad)
14    public void badExample2(String filename) {
15        try {
16            // File operations
17        } catch (Exception e) {  // Too broad - catches everything!
18            System.out.println("Something went wrong");
19        }
20    }
21    
22    // Bad: Using exceptions for control flow
23    public boolean badExample3(String text) {
24        try {
25            Integer.parseInt(text);
26            return true;
27        } catch (NumberFormatException e) {
28            return false;  // Don't use exceptions for expected conditions
29        }
30    }
31    
32    // Better version
33    public boolean isInteger(String text) {
34        if (text == null || text.trim().isEmpty()) {
35            return false;
36        }
37        
38        try {
39            Integer.parseInt(text.trim());
40            return true;
41        } catch (NumberFormatException e) {
42            return false;
43        }
44    }
45}

Real-World Example: Configuration File Processor

Let’s put it all together with a realistic example:

  1import java.io.*;
  2import java.util.*;
  3
  4public class ConfigurationProcessor {
  5    
  6    public static class ConfigurationException extends Exception {
  7        public ConfigurationException(String message) {
  8            super(message);
  9        }
 10        
 11        public ConfigurationException(String message, Throwable cause) {
 12            super(message, cause);
 13        }
 14    }
 15    
 16    public static Map<String, String> loadConfiguration(String filename) throws ConfigurationException {
 17        Map<String, String> config = new HashMap<>();
 18        
 19        File file = new File(filename);
 20        if (!file.exists()) {
 21            throw new ConfigurationException("Configuration file not found: " + filename);
 22        }
 23        
 24        if (!file.canRead()) {
 25            throw new ConfigurationException("Cannot read configuration file: " + filename);
 26        }
 27        
 28        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
 29            String line;
 30            int lineNumber = 0;
 31            
 32            while ((line = reader.readLine()) != null) {
 33                lineNumber++;
 34                
 35                // Skip comments and empty lines
 36                line = line.trim();
 37                if (line.isEmpty() || line.startsWith("#")) {
 38                    continue;
 39                }
 40                
 41                // Parse key=value pairs
 42                int equalsIndex = line.indexOf('=');
 43                if (equalsIndex == -1) {
 44                    throw new ConfigurationException(
 45                        String.format("Invalid configuration format at line %d: '%s'", lineNumber, line)
 46                    );
 47                }
 48                
 49                String key = line.substring(0, equalsIndex).trim();
 50                String value = line.substring(equalsIndex + 1).trim();
 51                
 52                if (key.isEmpty()) {
 53                    throw new ConfigurationException(
 54                        String.format("Empty key at line %d", lineNumber)
 55                    );
 56                }
 57                
 58                config.put(key, value);
 59            }
 60            
 61        } catch (IOException e) {
 62            throw new ConfigurationException("Error reading configuration file: " + filename, e);
 63        }
 64        
 65        return config;
 66    }
 67    
 68    public static void saveConfiguration(String filename, Map<String, String> config) throws ConfigurationException {
 69        // Create backup first
 70        File originalFile = new File(filename);
 71        if (originalFile.exists()) {
 72            File backupFile = new File(filename + ".backup");
 73            try {
 74                Files.copy(originalFile.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
 75            } catch (IOException e) {
 76                throw new ConfigurationException("Failed to create backup", e);
 77            }
 78        }
 79        
 80        // Write to temporary file first
 81        String tempFilename = filename + ".tmp";
 82        try (PrintWriter writer = new PrintWriter(new FileWriter(tempFilename))) {
 83            writer.println("# Configuration file");
 84            writer.println("# Generated on: " + new Date());
 85            writer.println();
 86            
 87            for (Map.Entry<String, String> entry : config.entrySet()) {
 88                writer.println(entry.getKey() + "=" + entry.getValue());
 89            }
 90            
 91        } catch (IOException e) {
 92            throw new ConfigurationException("Error writing configuration", e);
 93        }
 94        
 95        // Atomic rename
 96        File tempFile = new File(tempFilename);
 97        if (!tempFile.renameTo(new File(filename))) {
 98            tempFile.delete();
 99            throw new ConfigurationException("Failed to save configuration file");
100        }
101    }
102    
103    public static void demonstrateConfigProcessor() {
104        System.out.println("=== Configuration Processor Demo ===");
105        
106        // Create sample config file
107        String configFile = "app.properties";
108        Map<String, String> sampleConfig = new HashMap<>();
109        sampleConfig.put("database.host", "localhost");
110        sampleConfig.put("database.port", "5432");
111        sampleConfig.put("app.name", "My Application");
112        sampleConfig.put("debug.enabled", "true");
113        
114        try {
115            // Save configuration
116            saveConfiguration(configFile, sampleConfig);
117            System.out.println("Configuration saved successfully");
118            
119            // Load configuration
120            Map<String, String> loadedConfig = loadConfiguration(configFile);
121            System.out.println("Configuration loaded: " + loadedConfig.size() + " properties");
122            
123            for (Map.Entry<String, String> entry : loadedConfig.entrySet()) {
124                System.out.println("  " + entry.getKey() + " = " + entry.getValue());
125            }
126            
127        } catch (ConfigurationException e) {
128            System.out.println("Configuration error: " + e.getMessage());
129            if (e.getCause() != null) {
130                System.out.println("Root cause: " + e.getCause().getMessage());
131            }
132        }
133        
134        // Test with invalid file
135        try {
136            loadConfiguration("non_existent_config.properties");
137        } catch (ConfigurationException e) {
138            System.out.println("Expected error: " + e.getMessage());
139        }
140    }
141}

The Bottom Line

Exception handling isn’t just about preventing crashes - it’s about creating applications that behave predictably when things go wrong. The key principles are:

  1. Fail fast, fail clearly - catch problems early and give useful error messages
  2. Clean up resources - always close files, connections, and other resources
  3. Handle exceptions at the right level - don’t catch what you can’t handle
  4. Provide context - include relevant information in error messages
  5. Log appropriately - record errors for debugging without overwhelming users

Remember, good exception handling is like good insurance - you hope you never need it, but when you do, you’re really glad it’s there. Start building these habits now, and your future self (and your teammates) will thank you.

The most important thing? Don’t be afraid of exceptions. They’re not failures - they’re opportunities to make your code more robust and user-friendly. Every exception you handle properly is one less bug report you’ll get later.