هرمية الاستثناءات
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
In the last lesson you met several exceptions with different names and different causes. They are not a random pile. Java arranges them in a family tree, and where an exception sits in the tree decides how the compiler treats it and how you should respond.
Staying with our fire-alarm picture: a building has different alarms for different dangers. Some mean "a person lit a toaster; handle it locally" and some mean "the building is on fire; get out". And some alarms are so serious that the safety law says every room must have a written plan for them. The exception tree is the building's alarm chart.
This lesson teaches you to place any exception in one of three groups: checked exceptions, unchecked exceptions (runtime exceptions), and errors. You will see how the compiler treats each, and a rule of thumb for choosing one when you design your own.
Why this matters: the group decides whether the compiler forces you to deal with a problem. Misreading it is behind both "my code will not compile" confusion and the habit of catching things that should never be caught.
This lesson builds on inheritance (BEG-022), where you saw extends, superclasses, and the way every class descends from Object, and on the field guide from "What goes wrong at runtime".
Learning Objectives
By the end of this lesson you will be able to:
- Draw the top of the tree:
Throwable,Error,Exception,RuntimeException. - Classify any exception as checked, unchecked, or an error by following its superclasses.
- Explain what the compiler requires for each group.
- Say why code should not try to recover from an
Error. - Choose between checked and unchecked for a new exception using the rule of thumb.
- Recognize the compile error produced by an unhandled checked exception.
Written Explanation
The tree
Every exception and error in Java is a class, and all of them descend from one class: java.lang.Throwable. The Java API documentation says only Throwable and its subclasses can be thrown with throw or named in a catch. Below it the tree splits:
Object
Throwable
Error (serious problems with the JVM or system)
OutOfMemoryError, StackOverflowError, ...
Exception (problems your program may meet)
IOException (checked)
FileNotFoundException
... other checked exceptions
RuntimeException (unchecked)
IllegalArgumentException
NumberFormatException
IllegalStateException
NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException (via IndexOutOfBoundsException)You can check any exception's ancestry with a short program that follows getSuperclass() upward. Real output for three types:
public class Chain {
static void show(Throwable t) {
StringBuilder sb = new StringBuilder(t.getClass().getSimpleName());
Class<?> c = t.getClass().getSuperclass();
while (c != null) {
sb.append(" -> ").append(c.getSimpleName());
c = c.getSuperclass();
}
System.out.println(sb);
}
public static void main(String[] args) {
show(new NumberFormatException());
show(new java.io.FileNotFoundException());
show(new StackOverflowError());
}
}NumberFormatException -> IllegalArgumentException -> RuntimeException -> Exception -> Throwable -> Object
FileNotFoundException -> IOException -> Exception -> Throwable -> Object
StackOverflowError -> VirtualMachineError -> Error -> Throwable -> Object(That program uses Class and getSuperclass() only to show the tree; you will not need them in everyday code.)
The three groups
The group of a class depends on one thing: which branch it descends from.
| Group | Descends from | The compiler... | Typical cause |
|---|---|---|---|
| Error | Error | does not force you to handle it | The JVM or system is in trouble |
| Unchecked exception | RuntimeException | does not force you to handle it | A bug in the program: wrong argument, null, bad index |
| Checked exception | Exception, but not RuntimeException | forces you to handle or declare it | A condition outside your control that the caller can often recover from |
The Java API documentation states this rule: Throwable and any subclass that is not also a subclass of RuntimeException or Error is a checked exception for compile-time purposes. Unchecked is the name for the other two groups together.
So a short test works for any class: walk up. If you meet Error, it is an error. If you meet RuntimeException, it is unchecked. If you reach Exception without meeting either, it is checked.
Errors: leave them alone
An Error signals a serious problem in the runtime or environment, such as OutOfMemoryError (the JVM could not get memory) or StackOverflowError (the call stack ran out, as in runaway recursion from BEG-014). The Oracle tutorial describes errors as external conditions an application usually cannot anticipate or recover from. By the time one is thrown, your program has probably lost the resources it needs to repair itself.
The practical rule: do not write code that catches Error. Fix the underlying cause instead, for example by adding the missing base case to a recursive method. A running server may end up logging the error and restarting; that is a decision for the platform, not for ordinary business code.
Unchecked exceptions: bugs
RuntimeException and its children usually mean the program has a bug: the caller passed null, an index is wrong, a value is out of the allowed range. The compiler does not make you declare or catch them, and the Oracle tutorial says it is usually better to fix the bug than to catch the exception.
You already met several. IllegalArgumentException and IllegalStateException, which you threw in the encapsulation module, are unchecked: the compiler never asked you to wrap withdraw in anything.
Checked exceptions: the compiler asks for a plan
A checked exception is a problem a well-written program should anticipate: a file may not exist, a network may be down. The compiler enforces what the Oracle tutorial calls the Catch or Specify Requirement: any code that can throw a checked exception must either catch it, or declare that the method may throw it. If you do neither, the code does not compile.
Here is the error. The program tries to throw an IOException and does nothing about it:
public class Bad {
public static void main(String[] args) {
System.out.println("Start");
throw new java.io.IOException("disk full");
}
}Real compiler output:
Bad.java:4: error: unreported exception IOException; must be caught or declared to be thrown
throw new java.io.IOException("disk full");
^
1 errorThe message tells you exactly what the language wants: caught (next lesson) or declared (the lesson after). Change the type to IllegalStateException and the same program compiles, runs, and prints Start followed by a stack trace, because no plan is required for unchecked exceptions.
Rule of thumb: which kind to choose
When you design your own exceptions later in this module, you will pick a branch. The traditional advice is:
- Checked when the caller can reasonably recover and the failure is part of the method's honest contract: "this file might not be there", "this account might not have enough money". The compiler makes sure nobody forgets the case.
- Unchecked for programming mistakes, such as passing
nullor a negative quantity. The caller should have checked first, and handling it everywhere would only add noise.
This is a guideline, not a law. The Oracle tutorial notes that some programmers use unchecked exceptions to avoid the requirement, but generally advises against that. Many modern frameworks also prefer unchecked exceptions in their APIs. Keep the guideline in mind and look at what each library or team does.
Examples
Example 1: sort five exceptions by walking up (simple)
Take ArithmeticException. Its parent is RuntimeException, so it is unchecked. Take OutOfMemoryError. Its ancestors include Error, so it is an error. Take FileNotFoundException. Parent: IOException, then Exception. It never meets RuntimeException or Error, so it is checked. Take NumberFormatException: parent IllegalArgumentException, then RuntimeException, so unchecked. Take InterruptedException: a direct child of Exception, so checked.
Example 2: let the program classify them (practical)
You can encode the "walk up" rule using instanceof, which you learned in the polymorphism module:
public class Kind {
static String kind(Throwable t) {
if (t instanceof Error) return "error";
if (t instanceof RuntimeException) return "unchecked";
return "checked";
}
public static void main(String[] args) {
Throwable[] samples = {
new java.io.FileNotFoundException(),
new ArithmeticException(),
new StackOverflowError(),
new NumberFormatException(),
new InterruptedException(),
new IllegalStateException()
};
for (Throwable t : samples) {
System.out.println(t.getClass().getSimpleName() + ": " + kind(t));
}
}
}Real output:
FileNotFoundException: checked
ArithmeticException: unchecked
StackOverflowError: error
NumberFormatException: unchecked
InterruptedException: checked
IllegalStateException: uncheckedThis is only a teaching helper: the compiler, not your code, applies the rule in real programs. The order matters, because a type that is neither an Error nor a RuntimeException falls through to "checked".
Example 3: designing the bank's two failures (realistic)
The bank has two kinds of failure.
- A teller's code calls
deposit(-50). Nobody can legitimately deposit a negative amount, so the caller made a mistake. This is a bug:IllegalArgumentException, unchecked. - A customer asks for 500 from an account holding 70. Nothing is wrong with the code: it is a normal business situation, the caller can show a message and carry on. This is a good candidate for a checked exception, so that every caller must decide what to do. Until now we used
IllegalStateExceptionfor this. In lesson 5 we will replace it with a dedicated checked exception.
Seeing the difference is the skill: one failure means "your code is wrong" and the other means "the request cannot be met right now".
Backend Insight
Frameworks you will use later lean on this tree. A web service typically treats a failure in the request (bad input, a rule violated) differently from a failure of the system (database unreachable). Both can be exceptions, but the first yields a "client error" response and the second a "server error" response. Knowing the family of an exception tells you where to look: a NumberFormatException points at the caller's data, while an OutOfMemoryError points at the server's resources.
Transaction handling follows the same split. In Spring, which you will meet later in the roadmap, a transaction by default rolls back on unchecked exceptions and errors but not on checked ones. We mention this only to show that the three groups matter in real jobs; treat the detail as something to confirm in that framework's documentation when you reach it.
Common Mistakes
Mistake 1: Assuming "checked" means "serious"
- Incorrect: "A
NullPointerExceptionmust be worse than anIOExceptionbecause it crashes the program." - Correct: "Checked and unchecked say who is forced to plan, not how bad it is." A checked exception is often routine.
Mistake 2: Catching Error or Throwable to be safe
Code that catches Throwable also catches every Error, including ones the program cannot recover from. Catch the narrowest type you can handle meaningfully, a theme of the next lesson.
Mistake 3: Using unchecked exceptions to silence the compiler
Switching an IOException to a RuntimeException only so the compiler stops complaining does not remove the problem. It removes the reminder. Choose the type by who can recover, not by what makes the build pass.
Mistake 4: Judging the group by the name
InterruptedException is checked and IllegalStateException is unchecked, and neither name says so. Walk up the tree, or look at the API documentation, to be sure.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Which class is the root of everything that can be thrown?
أظهر الحل A1
A1. java.lang.Throwable.
A2. Name the three groups and say which ones the compiler forces you to handle.
أظهر الحل A2
A2. Checked exceptions, unchecked exceptions (descendants of RuntimeException), and errors. The compiler forces you to handle or declare only the checked ones.
A3. Classify each as checked, unchecked, or error: IllegalStateException, FileNotFoundException, StackOverflowError.
أظهر الحل A3
A3. IllegalStateException: unchecked. FileNotFoundException: checked. StackOverflowError: error.
Level B: Application
B1. This program does not compile. State why in one sentence, and give two different ways to make it compile (describe them; you do not need the syntax for one of them yet).
public class Task {
public static void main(String[] args) {
throw new Exception("not ready");
}
}أظهر الحل B1
B1. It throws Exception, which is checked, but main neither catches nor declares it. Way 1: wrap the throw in a try/catch (next lesson). Way 2: declare that main may throw it, with a throws Exception clause (lesson 4). Changing it to an unchecked type such as IllegalStateException also compiles, but only if the problem really is a bug.
B2. A teammate writes catch (Throwable t) around a call to a recursive method "just in case". Explain two problems with this.
أظهر الحل B2
B2. It also catches StackOverflowError or OutOfMemoryError, which the program usually cannot repair. And it hides the real cause: with no fix to the missing base case the code may fail again or leave the program in a damaged state.
B3. Using the walk-up test, classify NegativeArraySizeException, given only that its parent is RuntimeException, and TimeoutException, given only that its parent is Exception.
أظهر الحل B3
B3. NegativeArraySizeException descends from RuntimeException, so it is unchecked. TimeoutException descends from Exception without passing through RuntimeException or Error, so it is checked.
Level C: Challenge
C1. For each situation, choose checked or unchecked and justify it in one sentence: (a) a method receives a null customer; (b) a method tries to read a data file that the user may have moved; (c) a method is asked to set a price of -1.
أظهر الحل C1
C1. (a) Unchecked: passing null is the caller's mistake. (b) Checked: a missing file is a normal condition and the caller can ask for another path. (c) Unchecked: a negative price is an invalid argument, so a bug in the caller.
C2. Write a short program like Kind that prints the kind of ClassCastException, IOException, and AssertionError. Predict the output before running it, then verify.
أظهر الحل C2
C2. Prediction: ClassCastException unchecked (a RuntimeException), IOException checked, AssertionError error (it descends from Error). Add the three instances to the samples array and run to verify.
Mini Challenge
Draw your own exception chart on paper or in a text file, with Throwable at the top and three columns below it: errors, checked, unchecked. Place at least eight exceptions in it, including six from this module. For each, add a five-word note on a typical cause. Then use the API documentation (searching for the class name will find it) to confirm two classes you were unsure about.
Check your work: every exception sits under the right branch, and none of your notes say that "checked means worse".
Summary & Next Lesson
Key takeaways
Throwableis the root. It splits intoErrorandException;RuntimeExceptionis a branch underException.- Errors: serious system problems. Do not catch them.
- Unchecked (
RuntimeExceptionand children): usually bugs. The compiler does not force a plan. - Checked (other
Exceptions): anticipated problems the caller may recover from. The compiler forces a catch or a declaration. - Rule of thumb: checked where the caller can recover, unchecked for programming mistakes. It is a guideline that teams apply in different ways.
Next lesson
You can now tell which exceptions the compiler will ask you to plan for. The next lesson, "try, catch, finally", teaches the plan itself: how to catch an exception and keep the program running.
Self-check
- Which two classes sit directly under
Throwablein this lesson? - How can you tell whether a class is a checked exception?
- Is
NumberFormatExceptionchecked or unchecked, and why? - What does the compiler say if you throw a checked exception without a plan?
- Why should ordinary code not catch
OutOfMemoryError?
أظهر الإجابات
ErrorandException.- Walk up its superclasses: if it reaches
Exceptionwithout passing throughRuntimeExceptionorError, it is checked. - Unchecked, because it descends from
IllegalArgumentException, which descends fromRuntimeException. unreported exception ...; must be caught or declared to be thrown.- It signals a system-level failure the program usually cannot repair, so catching it only hides the problem.
References
- Oracle, The Java Tutorials, The Catch or Specify Requirement. Fetched. Supports the three kinds (checked, error, runtime), which of them the compiler enforces, and the advice to fix runtime-exception bugs and avoid using unchecked exceptions only to skip the requirement.
- Oracle, Java SE 21 API, Throwable. Fetched. Supports that only throwables can be thrown or caught, and the rule that any throwable not under
RuntimeExceptionorErroris checked. - The ancestry chains, the classification output, and the compiler message above were produced with JDK 21.0.10.
- Not verified: the statement about default rollback behavior in Spring is from general knowledge and is hedged in the text; confirm it in the Spring documentation when you reach that module. The parent classes given in exercise B3 are supplied as assumptions in the question. The C2 answer was run.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.