{"id":"exceptions-details","slug":"details-of-an-exception","title":"The details of an exception","text":"The `e` in `catch (NumberFormatException e)` is an object, and it knows more than its class. This last step is about what it can tell you, and how to read the report a crash leaves behind.\n\n### getMessage, and printing e\n\n```java\ntry {\n    Integer.parseInt(\"12 kg\");\n} catch (NumberFormatException e) {\n    System.out.println(e.getMessage());\n    System.out.println(e);\n}\nint[] floors = {0, 1, 2};\ntry {\n    System.out.println(floors[5]);\n} catch (ArrayIndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\nIllegalArgumentException plain = new IllegalArgumentException();\nSystem.out.println(plain.getMessage());\nSystem.out.println(plain);\n```\n\n```output\nFor input string: \"12 kg\"\njava.lang.NumberFormatException: For input string: \"12 kg\"\nIndex 5 out of bounds for length 3\nnull\njava.lang.IllegalArgumentException\n```\n\n- `e.getMessage()` returns the message: the text after the colon in a crash report.\n- Printing `e` itself uses the exception's `toString`: its class, with the package, then a colon and the message. That's the first line of a crash report, without the `Exception in thread \"main\"` in front.\n- An exception created without a message, such as `new IllegalArgumentException()`, has `null` as its message, and printing it shows only its class. That's one reason to always give your own exceptions a message.\n\nWhich one should a program show? Your own messages, such as `there's no seat 25`, are written for the people who use the program, so `getMessage()` fits them. Java's messages are written for programmers: `For input string: \"12 kg\"` doesn't tell a user what to do, so a program usually prints a text of its own instead, as the first step did. Printing `e` is handy while you hunt for a bug, since it names the kind of exception too.\n\n### The stack trace\n\nThe `at` lines of a crash report are the **stack trace**: the methods that were running when the exception was created, the newest first. Each line names a class and a method, and in parentheses the file and the line. This program finds the last bus of the day from a list of departure times, and one of the times is written wrong:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        String[] departures = {\"8:15\", \"9:40\", \"10h05\"};\n        System.out.println(\"Last departure: \" + latest(departures));\n    }\n\n    public static int latest(String[] times) {\n        int latest = 0;\n        for (String time : times) {\n            latest = Math.max(latest, minutesOf(time));\n        }\n        return latest;\n    }\n\n    public static int minutesOf(String time) {\n        String[] parts = time.split(\":\");\n        return Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]);\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.lang.NumberFormatException: For input string: \"10h05\"\n\tat Main.minutesOf(Main.java:17)\n\tat Main.latest(Main.java:10)\n\tat Main.main(Main.java:4)\n```\n\nRead it from the top:\n\n- `at Main.minutesOf(Main.java:17)`: the exception was thrown on line 17, in `minutesOf`, by `parseInt`. `10h05` has no colon, so `parts[0]` is the whole text.\n- `at Main.latest(Main.java:10)`: `minutesOf` was called on line 10, in `latest`'s loop.\n- `at Main.main(Main.java:4)`: and `latest` was called on line 4 of `main`, where it all started.\n\nThe top line says where it happened, and the lines below it say how the program got there. You can also read it from the bottom up, as the MOOC does: `main` called `latest`, which called `minutesOf`. The fix isn't always on the top line: here the mistake is in the data, `10h05`. On your own computer, lines from inside Java's own classes, which start with `at java.base/`, come first; skip them to the first line from your own code.\n\nA `catch` block can print the whole report without stopping the program: `e.printStackTrace()`. It prints to the error stream, `System.err`, as a crash does. It's a tool for finding bugs; a finished program shows its users a message instead, so the programs of this course don't use it.\n\n### An exception class of your own\n\nJava's exceptions cover most needs, but a program can also define its own kind, as a class that extends Exception (for a checked one) or RuntimeException (for an unchecked one):\n\n```java\npublic class SeatTakenException extends RuntimeException {\n    public SeatTakenException(String message) {\n        super(message);\n    }\n}\n```\n\n`super(message)` hands the message to the parent's constructor (module 34). After that, `throw new SeatTakenException(\"seat 5 is taken\")` works like any other `throw`, and `catch (SeatTakenException e)` catches exactly this kind. This course won't need any: IllegalArgumentException and IllegalStateException say well enough what's wrong.\n\n### What this module covered\n\n- An exception is an object that describes a problem. It's thrown, travels up the call stack to a `catch` block for its class or a parent class, and stops the program with a report if nothing catches it.\n- `try` holds the code that might throw. Several `catch` blocks go from the most specific class to the most general, and `finally` runs whatever happens.\n- Checked exceptions, such as IOException, must be caught or declared with `throws`. Unchecked ones, RuntimeException and the classes below it, needn't be.\n- try-with-resources closes a file, or any other AutoCloseable, as soon as its block ends.\n- `throw new IllegalArgumentException(\"...\")` refuses a bad parameter, before anything is changed.\n- An interface's `throws` says what its implementations may throw: that, less, or nothing.\n- `getMessage()` is the message, printing `e` adds the class, and the stack trace shows where it happened and how the program got there.\n","task":"Here's a crash from a booking program you can't see. Fill in the blanks: the class whose code threw the exception, the line it was thrown on, and the line of `main` where it all started.\n\n```crash\nException in thread \"main\" java.lang.IllegalStateException: seat 12 is taken\n\tat SeatMap.reserve(SeatMap.java:14)\n\tat TicketOrder.<init>(TicketOrder.java:9)\n\tat Main.main(Main.java:6)\n```\n\nThe program then prints:\n\n```\nThrown in SeatMap on line 14, started on line 6 of main\n```\n","kind":"fill","seed":"public class Main {\n    public static void main(String[] args) {\n        String thrownIn = \"[[SeatMap]]\";\n        int thrownOn = [[14]];\n        int mainLine = [[6]];\n        System.out.println(\"Thrown in \" + thrownIn + \" on line \" + thrownOn + \", started on line \" + mainLine + \" of main\");\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String thrownIn = \"SeatMap\";\n        int thrownOn = 14;\n        int mainLine = 6;\n        System.out.println(\"Thrown in \" + thrownIn + \" on line \" + thrownOn + \", started on line \" + mainLine + \" of main\");\n    }\n}\n","hints":["The top `at` line is where the exception was thrown: the class and the method come before the parentheses, the file and the line inside them.","The bottom line is in `main`. `<init>` in the middle line is a constructor: TicketOrder's constructor called `reserve`."],"tests":[{"name":"Output","stdin":"","expect":"Thrown in SeatMap on line 14, started on line 6 of main","hidden":false}],"require":[],"forbid":[],"more":[{"task":"An elevator program for a building with floors 0 to 5. What does it print? Mind which lines print `e` and which print `e.getMessage()`.\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        String[] inputs = {\"3rd\", \"7\"};\n        for (String input : inputs) {\n            try {\n                int floor = Integer.parseInt(input);\n                if (floor > 5) {\n                    throw new IllegalArgumentException(\"No floor \" + floor);\n                }\n                System.out.println(\"Going to \" + floor);\n            } catch (NumberFormatException e) {\n                System.out.println(e);\n            } catch (IllegalArgumentException e) {\n                System.out.println(e.getMessage());\n            }\n        }\n        IllegalStateException stuck = new IllegalStateException();\n        System.out.println(stuck.getMessage() + \" / \" + stuck);\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String[] inputs = {\"3rd\", \"7\"};\n        for (String input : inputs) {\n            try {\n                int floor = Integer.parseInt(input);\n                if (floor > 5) {\n                    throw new IllegalArgumentException(\"No floor \" + floor);\n                }\n                System.out.println(\"Going to \" + floor);\n            } catch (NumberFormatException e) {\n                System.out.println(e);\n            } catch (IllegalArgumentException e) {\n                System.out.println(e.getMessage());\n            }\n        }\n        IllegalStateException stuck = new IllegalStateException();\n        System.out.println(stuck.getMessage() + \" / \" + stuck);\n    }\n}\n","hints":["`3rd` isn't a number. The first `catch` block prints the exception itself: its class with the package, a colon, and the message `For input string: \"3rd\"`.","7 is a number, but the program throws its own IllegalArgumentException, and the second block prints only its message. An exception made without a message has `null` as its message, and printing it shows only its class."],"tests":[{"name":"Output","stdin":"","expect":"java.lang.NumberFormatException: For input string: \"3rd\"\nNo floor 7\nnull / java.lang.IllegalStateException","hidden":false}],"require":[],"forbid":[],"lines":["java.lang.NumberFormatException: For input string: \"3rd\"","No floor 7","null / java.lang.IllegalStateException"]},{"task":"The closing challenge puts the module together. A home heating system reads its schedule from `schedule.txt`. Each line has a time and a temperature, separated by a space, such as `06:30 21`. `Heating.java` is ready: `add(time, degrees)` adds a change to the schedule, and throws an IllegalArgumentException when the temperature isn't between 5 and 30 degrees; `count()` returns how many changes it has.\n\nWrite `main`. Read the file line by line with a Scanner in a try-with-resources, and for each line:\n\n- add it to the Heating, and print the time, ` -> ` and the temperature, like `06:30 -> 21`\n- if the temperature isn't a whole number, print `Line ` and the line's number, then `: not a number: ` and the temperature, like `Line 4: not a number: warm`\n- if `add` refuses it, print `Line `, the line's number, `: ` and the exception's message\n\nWhen the file can't be read, print `Could not read schedule.txt`. At the very end, print `Changes: ` and how many changes the Heating has. Every line of the file has a time, a space and a temperature. For the file shown below, the program prints:\n\n```\n06:30 -> 21\n08:00 -> 17\nLine 3: 45 degrees is not a room temperature\nLine 4: not a number: warm\n22:00 -> 18\nChanges: 3\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Heating heating = new Heating();\n\n    }\n}\n\n// ==== Heating.java ====\nimport java.util.ArrayList;\n\npublic class Heating {\n    private ArrayList<String> changes;\n\n    public Heating() {\n        this.changes = new ArrayList<>();\n    }\n\n    public void add(String time, int degrees) {\n        if (degrees < 5 || degrees > 30) {\n            throw new IllegalArgumentException(degrees + \" degrees is not a room temperature\");\n        }\n        this.changes.add(time + \" \" + degrees);\n    }\n\n    public int count() {\n        return this.changes.size();\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Heating heating = new Heating();\n        try (Scanner scanner = new Scanner(Paths.get(\"schedule.txt\"))) {\n            int number = 0;\n            while (scanner.hasNextLine()) {\n                number++;\n                String[] parts = scanner.nextLine().split(\" \");\n                try {\n                    int degrees = Integer.parseInt(parts[1]);\n                    heating.add(parts[0], degrees);\n                    System.out.println(parts[0] + \" -> \" + degrees);\n                } catch (NumberFormatException e) {\n                    System.out.println(\"Line \" + number + \": not a number: \" + parts[1]);\n                } catch (IllegalArgumentException e) {\n                    System.out.println(\"Line \" + number + \": \" + e.getMessage());\n                }\n            }\n        } catch (Exception e) {\n            System.out.println(\"Could not read schedule.txt\");\n        }\n        System.out.println(\"Changes: \" + heating.count());\n    }\n}\n\n// ==== Heating.java ====\nimport java.util.ArrayList;\n\npublic class Heating {\n    private ArrayList<String> changes;\n\n    public Heating() {\n        this.changes = new ArrayList<>();\n    }\n\n    public void add(String time, int degrees) {\n        if (degrees < 5 || degrees > 30) {\n            throw new IllegalArgumentException(degrees + \" degrees is not a room temperature\");\n        }\n        this.changes.add(time + \" \" + degrees);\n    }\n\n    public int count() {\n        return this.changes.size();\n    }\n}\n","hints":["The Heating is made before the `try`, so it still exists afterwards for the count. Inside `try (Scanner scanner = new Scanner(Paths.get(\"schedule.txt\"))) { ... }`, count the lines in a variable as you read them.","Give each line a `try` of its own: `Integer.parseInt(parts[1])`, then `heating.add(...)`, then the `println`. Catch `NumberFormatException` first and `IllegalArgumentException` second. The outer `try` gets a `catch (Exception e)` for the file, and `Changes: ` is printed after it."],"tests":[{"name":"Test 1","stdin":"","files":{"schedule.txt":"06:30 21\n08:00 17\n16:30 45\n17:00 warm\n22:00 18\n"},"expect":"06:30 -> 21\n08:00 -> 17\nLine 3: 45 degrees is not a room temperature\nLine 4: not a number: warm\n22:00 -> 18\nChanges: 3","hidden":false},{"name":"Without the file","stdin":"","expect":"Could not read schedule.txt\nChanges: 0","hidden":true},{"name":"Test 3","stdin":"","files":{"schedule.txt":"07:00 19\n07:30 4\n21:15 31\n23:00 16\n"},"expect":"07:00 -> 19\nLine 2: 4 degrees is not a room temperature\nLine 3: 31 degrees is not a room temperature\n23:00 -> 16\nChanges: 2","hidden":true},{"name":"Test 4","stdin":"","files":{"schedule.txt":"05:45 20\n12:00 20.5\n18:10 22\n"},"expect":"05:45 -> 20\nLine 2: not a number: 20.5\n18:10 -> 22\nChanges: 2","hidden":true}],"require":[{"pattern":"\\btry\\s*\\(\\s*Scanner\\s+\\w+\\s*=\\s*new\\s+Scanner\\s*\\(","message":"Read the file with a Scanner created in the parentheses after `try`."},{"pattern":"\\.getMessage\\s*\\(","message":"Print the refusal's own message with `e.getMessage()`."}],"forbid":[]}]}