{"id":"exceptions-choosing","slug":"choosing-what-to-catch","title":"Choosing what to catch","text":"A time of day such as `7:45` is easy for people to read. A program that compares or adds up times often turns it into a single number, the minutes after midnight: 7 hours and 45 minutes is 7 * 60 + 45 = 465. Two different things can go wrong on the way, and each throws its own exception:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        String[] times = {\"7:45\", \"745\", \"7:4five\"};\n        for (String time : times) {\n            try {\n                String[] parts = time.split(\":\");\n                int hours = Integer.parseInt(parts[0]);\n                int minutes = Integer.parseInt(parts[1]);\n                System.out.println(time + \" is \" + (hours * 60 + minutes) + \" minutes after midnight\");\n            } catch (NumberFormatException e) {\n                System.out.println(time + \": the hours and minutes must be numbers\");\n            } catch (ArrayIndexOutOfBoundsException e) {\n                System.out.println(time + \": write the time as hours:minutes\");\n            }\n        }\n    }\n}\n```\n\n```output\n7:45 is 465 minutes after midnight\n745: write the time as hours:minutes\n7:4five: the hours and minutes must be numbers\n```\n\n- `745` has no colon, so `split` gives just one part, and `parts[1]` throws an `ArrayIndexOutOfBoundsException` (module 15).\n- In `7:4five`, the minutes aren't a number, and `parseInt` throws a `NumberFormatException`.\n\nA `try` can have several `catch` blocks, one after the other. When an exception is thrown, Java checks them from the top and runs the **first** one whose class matches. At most one `catch` block runs, and then the program goes on after the last one.\n\n### A family of classes\n\nEach kind of exception is a class, and the classes inherit from each other, just like the classes of module 34. Here's a part of the family, each class under its parent:\n\n- `Exception`\n    - `IOException`: reading or writing data failed. Reading a missing file the way this course does, with `Paths.get(...)`, throws a `NoSuchFileException`, one of its descendants. Some other ways of opening a file throw a `FileNotFoundException` instead, also an IOException. Module 43 meets it.\n    - `RuntimeException`\n        - `ArithmeticException`, such as `/ by zero`\n        - `IllegalArgumentException`: a method got a value it can't work with\n            - `NumberFormatException`\n        - `IndexOutOfBoundsException`\n            - `ArrayIndexOutOfBoundsException` and `StringIndexOutOfBoundsException`\n        - `NullPointerException`\n\nA NumberFormatException **is an** IllegalArgumentException, which is a RuntimeException, which is an Exception. So a `catch` block catches its own class and every class below it:\n\n- `catch (IndexOutOfBoundsException e)` catches the index problems of lists, arrays and Strings.\n- `catch (RuntimeException e)` catches everything in its branch.\n- `catch (Exception e)`, as in module 20, catches all of them.\n\nCatch a parent class when every child gets the same reaction, and separate classes when each problem needs its own. If one message covers both problems with the times, one block is enough:\n\n```java\nString[] times = {\"745\", \"7:4five\"};\nfor (String time : times) {\n    try {\n        String[] parts = time.split(\":\");\n        int minutes = Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]);\n        System.out.println(minutes);\n    } catch (RuntimeException e) {\n        System.out.println(time + \" isn't a time like 7:45\");\n    }\n}\n```\n\n```output\n745 isn't a time like 7:45\n7:4five isn't a time like 7:45\n```\n\n### The more specific class first\n\nSince a parent class catches its children, the order of the `catch` blocks matters. A `catch` for a child class after its parent's could never run, and javac refuses it:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        try {\n            int minutes = Integer.parseInt(\"4five\");\n            System.out.println(minutes);\n        } catch (IllegalArgumentException e) {\n            System.out.println(\"That's not a valid value\");\n        } catch (NumberFormatException e) {\n            System.out.println(\"That's not a number\");\n        }\n    }\n}\n```\n\n```javac\nMain.java:8: error: exception NumberFormatException has already been caught\n        } catch (NumberFormatException e) {\n          ^\n1 error\n```\n\nSwap the two blocks, and it compiles: a NumberFormatException then goes to its own block, and any other IllegalArgumentException to the second one. A common pattern is the specific classes first and a `catch (Exception e)` at the end, for anything else.\n\n### finally\n\nA `try` can end with a `finally` block. It runs after the `try` block and the `catch` block, whatever happened: when nothing was thrown, when an exception was caught, and even when the `try` or `catch` block leaves the method with `return`:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(minutesOf(\"7:45\"));\n        System.out.println(minutesOf(\"745\"));\n    }\n\n    public static int minutesOf(String time) {\n        try {\n            String[] parts = time.split(\":\");\n            return Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]);\n        } catch (RuntimeException e) {\n            return -1;\n        } finally {\n            System.out.println(\"checked \" + time);\n        }\n    }\n}\n```\n\n```output\nchecked 7:45\n465\nchecked 745\n-1\n```\n\nThe `return` decides the value, then the `finally` block runs, and only then does the method really return. That's why `checked 7:45` comes before `main` prints 465. An exception that no `catch` block catches runs the `finally` block too, on its way out of the method.\n\n`finally` is for work that must be done however the `try` block ends, such as giving back something the program borrowed. The most common case, closing a file, has a shorter form of its own, in step 4.\n","task":"A kitchen timer is set with minutes and seconds with a colon between them, such as `2:30`. The program below reads one line and prints the time in seconds, but it crashes on bad input. Add a `try` with two `catch` blocks, so that:\n\n- a line without a colon, such as `150`, prints `Use minutes:seconds, like 2:30`\n- a line whose parts aren't whole numbers, such as `2:3o`, prints `Minutes and seconds must be whole numbers`\n\nA good time still prints the seconds: `Timer set for 150 seconds` for `2:30`. With the input `150` the program prints:\n\n```\nUse minutes:seconds, like 2:30\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        String time = scanner.nextLine();\n        String[] parts = time.split(\":\");\n        int minutes = Integer.parseInt(parts[0]);\n        int seconds = Integer.parseInt(parts[1]);\n        System.out.println(\"Timer set for \" + (minutes * 60 + seconds) + \" seconds\");\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        String time = scanner.nextLine();\n        try {\n            String[] parts = time.split(\":\");\n            int minutes = Integer.parseInt(parts[0]);\n            int seconds = Integer.parseInt(parts[1]);\n            System.out.println(\"Timer set for \" + (minutes * 60 + seconds) + \" seconds\");\n        } catch (ArrayIndexOutOfBoundsException e) {\n            System.out.println(\"Use minutes:seconds, like 2:30\");\n        } catch (NumberFormatException e) {\n            System.out.println(\"Minutes and seconds must be whole numbers\");\n        }\n    }\n}\n","hints":["Put the lines from `split` to the `println` inside `try { ... }`.","`parts[1]` throws an `ArrayIndexOutOfBoundsException` when there's no colon, and `Integer.parseInt` throws a `NumberFormatException`: one `catch` block for each."],"tests":[{"name":"Test 1","stdin":"150\n","expect":"Use minutes:seconds, like 2:30","hidden":false},{"name":"Test 2","stdin":"2:30\n","expect":"Timer set for 150 seconds","hidden":false},{"name":"Test 3","stdin":"2:3o\n","expect":"Minutes and seconds must be whole numbers","hidden":true},{"name":"Test 4","stdin":"one:30\n","expect":"Minutes and seconds must be whole numbers","hidden":true},{"name":"Test 5","stdin":"0:45\n","expect":"Timer set for 45 seconds","hidden":true}],"require":[{"pattern":"\\bcatch\\s*\\(","min":2,"message":"Use two `catch` blocks, one for each kind of problem."}],"forbid":[],"more":[{"task":"What does this program print? Keep in mind which exception class is a child of which.\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        check(\"4\");\n        check(\"0\");\n        check(\"four\");\n    }\n\n    public static void check(String text) {\n        try {\n            int people = Integer.parseInt(text);\n            System.out.println(12 / people);\n        } catch (ArithmeticException e) {\n            System.out.println(\"nobody\");\n        } catch (RuntimeException e) {\n            System.out.println(\"not a number\");\n        } finally {\n            System.out.println(\"done\");\n        }\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        check(\"4\");\n        check(\"0\");\n        check(\"four\");\n    }\n\n    public static void check(String text) {\n        try {\n            int people = Integer.parseInt(text);\n            System.out.println(12 / people);\n        } catch (ArithmeticException e) {\n            System.out.println(\"nobody\");\n        } catch (RuntimeException e) {\n            System.out.println(\"not a number\");\n        } finally {\n            System.out.println(\"done\");\n        }\n    }\n}\n","hints":["`12 / 0` throws an ArithmeticException, and the first `catch` block matches it. A NumberFormatException isn't an ArithmeticException, but it is a RuntimeException, so the second block catches it.","The `finally` block runs at the end of every call, whether an exception was thrown or not."],"tests":[{"name":"Output","stdin":"","expect":"3\ndone\nnobody\ndone\nnot a number\ndone","hidden":false}],"require":[],"forbid":[],"lines":["3","done","nobody","done","not a number","done"]},{"task":"An online course shows your progress written as `done/total`, such as `7/20` for 7 lessons done out of 20. The program should print `35% done` for `7/20`, `The total can't be 0` when the total is 0, `Use whole numbers, like 7/20` when a part isn't a whole number, and `Write it as done/total, like 7/20` for any other problem. It doesn't compile. Read javac's messages and fix the program, without changing what any block prints. With the input `7/0` it prints:\n\n```\nThe total can't be 0\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        String line = scanner.nextLine();\n        try {\n            String[] parts = line.split(\"/\");\n            int done = Integer.parseInt(parts[0]);\n            int total = Integer.parseInt(parts[1]);\n            System.out.println(done * 100 / total + \"% done\");\n        } catch (Exception e) {\n            System.out.println(\"Write it as done/total, like 7/20\");\n        } catch (ArithmeticException e) {\n            System.out.println(\"The total can't be 0\");\n        } catch (NumberFormatException e) {\n            System.out.println(\"Use whole numbers, like 7/20\");\n        }\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        String line = scanner.nextLine();\n        try {\n            String[] parts = line.split(\"/\");\n            int done = Integer.parseInt(parts[0]);\n            int total = Integer.parseInt(parts[1]);\n            System.out.println(done * 100 / total + \"% done\");\n        } catch (ArithmeticException e) {\n            System.out.println(\"The total can't be 0\");\n        } catch (NumberFormatException e) {\n            System.out.println(\"Use whole numbers, like 7/20\");\n        } catch (Exception e) {\n            System.out.println(\"Write it as done/total, like 7/20\");\n        }\n    }\n}\n","hints":["javac says that the exceptions of the later blocks `have already been caught`. `catch (Exception e)` comes first, and it catches every exception, so the blocks after it could never run.","Move the `catch (Exception e)` block to the end, after the ArithmeticException and NumberFormatException blocks."],"tests":[{"name":"Test 1","stdin":"7/0\n","expect":"The total can't be 0","hidden":false},{"name":"Test 2","stdin":"7/20\n","expect":"35% done","hidden":false},{"name":"Test 3","stdin":"seven/20\n","expect":"Use whole numbers, like 7/20","hidden":true},{"name":"Test 4","stdin":"7\n","expect":"Write it as done/total, like 7/20","hidden":true},{"name":"Test 5","stdin":"20/20\n","expect":"100% done","hidden":true}],"require":[],"forbid":[]}]}