1. Introduction to Checked and Unchecked Exceptions

Java uses exceptions to represent conditions that interrupt the normal flow of a program. A file may not exist, a network connection may fail, a string may not contain a valid number, or code may attempt to use an object reference that is null. Although all these situations can interrupt execution, Java does not require programmers to handle every kind of exception in the same way.

The language separates exceptions into checked and unchecked categories. The key difference is not whether an exception is serious, whether it crashes the program, or whether it happens frequently. The important difference is whether the Java compiler requires the programmer to handle or declare it.

For example, reading a file with Java's file APIs can produce an IOException. Because this is a checked exception, code must either catch it or declare it with throws. By contrast, a NullPointerException is unchecked. The compiler allows code that could produce it to compile without a catch block or a throws declaration, although the program can still fail at runtime.

This distinction is central to Java exception handling. It affects method signatures, API design, inheritance, error recovery, readability, and the way responsibility for a failure moves through an application.

2. The Java Throwable Hierarchy

Every Java exception or error object belongs to a class hierarchy rooted at java.lang.Throwable. The compiler uses this hierarchy to decide whether handling is mandatory.

Whiteboard
Loading diagram...


Throwable has two direct subclasses: Exception and Error.

  • Exception represents conditions that application code may want to detect or respond to. Some subclasses are checked, while the RuntimeException branch is unchecked.
  • Error represents serious problems generally associated with the JVM or execution environment. Errors are unchecked, but they are normally not treated as ordinary recoverable application failures.
  • RuntimeException is the special branch within Exception whose subclasses are unchecked.

A class's position in this hierarchy—not its name, message, or apparent seriousness—determines whether it is checked or unchecked.

3. The Core Classification Rule

The classification can be understood through three rules:

  1. A class that extends RuntimeException, directly or indirectly, is unchecked.
  2. A class that extends Error, directly or indirectly, is also unchecked.
  3. A class that extends Exception but is not in the RuntimeException branch is generally checked.

This means IOException, SQLException, InterruptedException, and ClassNotFoundException are checked exceptions. NullPointerException, IllegalArgumentException, IllegalStateException, NumberFormatException, and ArithmeticException are unchecked exceptions. OutOfMemoryError and StackOverflowError belong to the unchecked Error family.

Do not classify exceptions by memorizing their names alone. Remember the inheritance rule. When designing a custom exception, the superclass you choose determines its category.

4. What a Checked Exception Means

A checked exception is an exception that the compiler tracks at compile time. When a method calls code that can propagate a checked exception, the calling method must make an explicit choice. It must either handle the exception locally or declare that it may pass the exception to its own caller.

This is known as Java's handle-or-declare rule.

  • Handle it: place the operation inside a suitable try block and provide a matching catch block.
  • Declare it: add the exception type to the calling method's throws clause, transferring the responsibility to the next caller.

If neither option is used, the compiler reports an unhandled or unreported exception error. The program does not compile until the issue is addressed.

A checked exception does not mean Java can guarantee that the program will recover. It means the compiler requires the code to acknowledge that the operation may fail in that declared way.

5. Example: Reading a File with IOException

File operations can fail for many reasons: the requested file may be absent, access may be denied, the storage device may encounter a problem, or an I/O operation may be interrupted. Java's file-reading APIs represent these failures using checked exceptions such as IOException.

Consider this simplified method:

The method declares throws IOException because it does not handle that exception itself. It tells callers that file reading may fail and that they must make their own handling decision.

A caller can allow the exception to propagate:

Or the caller can handle the problem locally:


Both approaches satisfy the compiler. The better choice depends on which method has enough context to decide what the application should do next. A low-level file utility may not know whether to retry, display an error, use cached data, or stop the operation. A higher-level application method may know.

The example uses Path.of, which is available in modern Java versions. In older Java versions, a different path-construction API may be needed.

6. What an Unchecked Exception Means

An unchecked exception is not subject to the compiler's handle-or-declare requirement. A method may produce an unchecked exception without declaring it in throws, and callers do not have to catch it merely to make the code compile.

Unchecked exceptions include all subclasses of RuntimeException, as well as all subclasses of Error. These are related by their unchecked status, but they represent different kinds of problems: runtime exceptions commonly indicate invalid input, invalid state, or programming defects, while errors commonly indicate more serious runtime or JVM-level conditions.

For example, parsing an invalid number can cause NumberFormatException:


This code compiles without a try/catch block. If the string is not a valid representation of a number, the exception occurs at runtime. Compilation therefore does not prove that unchecked exceptions cannot happen; it only means the compiler does not require them to be declared or caught.

A programmer may still catch an unchecked exception when there is a meaningful recovery strategy. The absence of a compiler requirement is not a recommendation to ignore the failure.

7. Checked vs Unchecked Exceptions: Comparison

AspectChecked exceptionsUnchecked exceptions
Main hierarchyException, excluding the RuntimeException branchRuntimeException and its subclasses; also Error and its subclasses
Compiler requires handling or declarationYes, when the checked exception can propagateNo
throws declarationRequired when propagating a checked exceptionOptional
Typical situationsFile, database, network, or interruption failuresInvalid arguments, invalid object state, null dereferences, programming defects
Caller responsibilityExplicitly handle or re-declareDecide whether handling is useful
Common examplesIOException, SQLException, InterruptedExceptionNullPointerException, IllegalArgumentException, NumberFormatException
Can terminate normal execution?YesYes
Does the category guarantee recoverability?NoNo

The table describes typical use, not an absolute law about every program. A checked exception may be difficult to recover from, and an unchecked exception may sometimes be handled meaningfully. The compiler rule is definitive; the design interpretation requires judgment.

8. Why Java Checks Some Exceptions but Not Others

The distinction reflects a design trade-off between explicit contracts and excessive boilerplate.

Some failures are ordinary possibilities when interacting with the outside world. A file may be missing, a database may be unavailable, or a network operation may fail. The caller may have several legitimate choices: retry, choose another resource, show a message, roll back a transaction, or use a fallback. Checked exceptions make those possible outcomes visible in method contracts and require callers to acknowledge them.

Other failures often point to a programming mistake or a violated method contract. A NullPointerException may mean that a required reference was never initialized. An IllegalArgumentException may mean that a caller supplied a value outside the supported range. If every method had to declare every possible null dereference, array-bound failure, and invalid state, method signatures would become noisy and difficult to use.

Unchecked exceptions avoid forcing every caller to add boilerplate for failures that may be better prevented by correct program design, validation, or a central application-level handler.

Neither approach is perfect. Checked exceptions improve visibility but can make APIs cumbersome when many layers merely pass exceptions upward. Unchecked exceptions simplify signatures but make some failure possibilities less explicit to callers. Good API design balances these costs.

9. Common Checked Exceptions

9.1 IOException

IOException represents failures during input and output operations. Examples include reading an unavailable file, writing to a resource that cannot accept data, or encountering an I/O problem during communication.

It is checked because the caller may be able to respond usefully. The application might ask for another file path, retry an operation, alert an operator, or cancel a workflow safely.

9.2 SQLException

SQLException is used by JDBC APIs for database-related failures. A database call may fail because a connection cannot be established, a statement is invalid, a constraint is violated, or a transaction encounters a problem.

The application may need to report the problem, roll back a transaction, retry under appropriate conditions, or translate the low-level database failure into an application-level error.

9.3 InterruptedException

InterruptedException indicates that a thread was interrupted while performing certain blocking operations, such as Thread.sleep, Object.wait, or relevant concurrency operations.

Interruption is commonly used as a cooperative cancellation signal. Code should not treat it as an ordinary failure to ignore. Depending on the method's role, it may propagate the exception or restore the thread's interrupt status after catching it, then exit or respond appropriately.

A common pattern when a method cannot propagate InterruptedException is:


Restoring the interrupt status preserves the cancellation signal for higher-level code. The exact response depends on the method's contract and whether it can declare InterruptedException.

9.4 ClassNotFoundException

ClassNotFoundException can occur when code attempts to load a class by name and the class cannot be found at runtime. The cause may be an incomplete classpath, a missing dependency, or an incorrect class name.

This is often an environmental or configuration problem, so a caller may be able to report a clear startup error or select an alternative implementation.

10. Common Unchecked Exceptions

ExceptionTypical causeUseful prevention or response
NullPointerExceptionCalling a method or accessing a field through a null referenceInitialize required references and validate nullable inputs
IllegalArgumentExceptionAn argument violates the method's documented requirementsValidate values at the boundary and document valid ranges
IllegalStateExceptionA method is called when an object is not in a valid stateEnforce lifecycle rules and check state transitions
ArrayIndexOutOfBoundsExceptionAn array index is outside the valid rangeCheck index calculations and collection sizes
ClassCastExceptionAn object is cast to an incompatible typeUse appropriate types, polymorphism, or safe type checks
ArithmeticExceptionInteger division or remainder by zeroValidate divisors where zero is not allowed
NumberFormatExceptionA string cannot be parsed into the requested numeric typeValidate external input and provide a useful message

10.1 NullPointerException

A NullPointerException often means code attempted to use a reference that does not refer to an object. For example, an order's customer name might be accessed before the customer object has been assigned.

The most useful fix is generally to find why the reference is null and correct the data flow or contract. Catching the exception close to the dereference may hide the underlying defect rather than solve it.

10.2 IllegalArgumentException

This exception commonly signals that a method received a value outside its supported input range. For example, a method that accepts a discount percentage from zero through one hundred may reject a value of 150.

The exception helps define the method's contract. Callers should supply valid arguments; the method should validate inputs when it owns that responsibility.

10.3 IllegalStateException

IllegalStateException is appropriate when the arguments may be valid but the object's current state makes the operation invalid. For example, a transaction might already be closed when code attempts to commit it again.

This differs from IllegalArgumentException: the issue is not necessarily the supplied value, but the timing or state of the operation.

10.4 NumberFormatException

NumberFormatException occurs when text cannot be interpreted as the requested number format. Parsing "29.99" as a decimal number can succeed, while parsing "banana" as a number fails.

If the text comes from a user, file, or API, validation and a clear input error may be appropriate. If the string was supposed to have been generated by trusted internal code, the exception may reveal a bug earlier in the data pipeline.

11. The Error Family: Unchecked, but Different

Error is separate from Exception. Its subclasses are unchecked, so the compiler does not require them to be caught or declared. However, errors commonly represent serious conditions that normal application code is not expected to recover from.

ErrorWhat it commonly indicates
OutOfMemoryErrorThe JVM cannot satisfy a memory allocation
StackOverflowErrorA thread's call stack has exceeded its limit, often due to runaway recursion
NoClassDefFoundErrorA class expected at runtime cannot be found or initialized as required
AssertionErrorAn assertion failed or an assertion error was thrown explicitly
LinkageErrorA class-linking or binary compatibility problem occurred

It is technically possible to catch an Error, but catching one does not necessarily make recovery safe. For example, after an OutOfMemoryError, code may not have enough memory to allocate objects needed for logging or recovery. A StackOverflowError may leave the current thread unable to perform much useful work.

Application code should generally fix the underlying condition rather than catch errors as if they were routine business failures. Frameworks, test runners, and process boundaries may catch Throwable in carefully controlled ways to prevent one failure from unnecessarily bringing down a larger host process. That is a specialized boundary responsibility, not a general recommendation to catch Throwable.

12. Choosing a Base Class for Custom Exceptions

When defining a custom exception, its superclass determines whether it is checked or unchecked.


InventoryShortageException is checked because it extends Exception outside the RuntimeException branch. Code that lets it propagate must handle or declare it. InvalidOrderStateException is unchecked because it extends RuntimeException, so callers are not forced by the compiler to catch or declare it.

Choose the superclass based on the intended contract, not on a belief that one category is universally better.

13. When a Checked Custom Exception Makes Sense

A checked custom exception is a reasonable choice when a failure is an expected part of an operation and the caller should explicitly consider the outcome. The caller should have a meaningful decision to make, such as retrying, selecting another option, returning a domain-specific response, or choosing a fallback.

For example, a reservation service may report that the requested quantity is unavailable. The checkout process could offer a smaller quantity, suggest a back-order, or stop the purchase with a clear message. Because the shortage is a normal business possibility rather than necessarily a programming bug, making it checked can encourage every relevant caller to account for it.

A checked exception is less attractive when every intermediate method has no useful response and simply repeats the same throws declaration. In that case, the compile-time burden may spread without improving recovery.

14. When an Unchecked Custom Exception Makes Sense

An unchecked custom exception often fits programming errors, violated invariants, or invalid object states. If an operation cannot sensibly proceed because a required internal condition is false, forcing every caller to catch the exception may add ceremony without creating a real recovery path.

For example, if a transaction has already been closed, attempting to commit it again may represent incorrect lifecycle usage. An unchecked InvalidOrderStateException or IllegalStateException can make that misuse visible without imposing a catch requirement on every call.

Unchecked exceptions can also suit domain failures handled consistently at a central boundary. A web application may use one controller-level handler to translate known exceptions into appropriate HTTP responses. Even then, the exception should be documented and the boundary handler should preserve enough information for diagnosis.

15. A Context-Dependent Example: OrderNotFoundException

Whether an order lookup should use a checked exception is not always obvious.

If the order ID comes from a URL or a form submitted by a user, a missing order may be an expected outcome. The application can show a not-found page, ask the user to check the identifier, or return a suitable API response. In that context, an explicit checked exception may be reasonable. Another option is to return Optional<Order>, which represents the possibility of absence without using an exception for ordinary lookup results.

If the order ID was generated internally a moment earlier and the order is unexpectedly missing, the same situation may indicate a broken invariant. An unchecked exception may be a better fit because the likely remedy is to investigate and correct the logic.

The exception type alone cannot determine the right design. The meaning of the failure at the call site matters.

16. A Practical Decision Flowchart for Custom Exceptions

Whiteboard
Loading diagram...


The flowchart is a guide rather than a rigid rule. Teams may reasonably choose different exception policies, especially for domain-specific failures. The most important goals are clarity, consistency, and ensuring that the layer responsible for a failure has enough information to respond.

17. Why Some Java Teams Prefer Unchecked Exceptions

Many modern Java teams lean toward unchecked exceptions in application and framework code, even when the failure is not strictly a programming bug. This preference has several practical causes.

17.1 Lambdas and Streams

Many standard functional interfaces used by streams, such as Function, Predicate, and Consumer, do not declare checked exceptions in their abstract method signatures. A lambda that directly invokes a method throwing a checked exception may therefore fail to compile unless the exception is handled or adapted.

This can lead to wrapper code that converts a checked exception into an unchecked exception. In complex pipelines, repeated wrapping can obscure the main logic. Some libraries provide helper methods or custom functional interfaces to make this easier.

This does not make checked exceptions invalid. It means their use across functional boundaries may require deliberate adaptation.

17.2 Asynchronous APIs

With APIs such as Future and CompletableFuture, a failure from asynchronous work may be exposed through wrapper exceptions such as ExecutionException or CompletionException. The caller must inspect the cause to understand the underlying problem.

Because the exception crosses an asynchronous boundary, declaring a checked exception on the method that submits work may not fully describe how the eventual failure will be observed. This is one reason teams sometimes prefer unchecked exceptions in asynchronous flows.

17.3 Framework Conventions

Some frameworks translate low-level checked exceptions into unchecked framework-specific exceptions at their boundaries. For example, a persistence framework may wrap JDBC failures so that application code can handle persistence errors through a common abstraction without declaring SQLException at every layer.

Translation can be useful when it adds meaningful context or creates a stable API. It should preserve the original exception as the cause so that diagnostic information is not lost.

17.4 The Trade-Off

The modern preference for unchecked exceptions is a design trend, not a Java language rule. The JDK continues to use checked exceptions in APIs where the contract benefits from explicit handling. Checked exceptions remain valuable when they make meaningful recovery decisions visible. Unchecked exceptions remain valuable when mandatory handling would create noise without improving the design.

18. A Common Mistake: Catching and Ignoring an Exception

A method may silence the compiler by catching an exception and doing nothing:


This code compiles, but it can make the failure harder to understand. If the file is missing or unreadable, the method returns null. A caller may later try to use the returned value and trigger a NullPointerException. The original I/O problem has been replaced by a less informative failure somewhere else in the call chain.

An empty catch block or a silent fallback is not automatically wrong in every imaginable situation, but it deserves careful scrutiny. A fallback should be an intentional part of the method's contract, not merely a way to make compilation succeed.

Better options include:

  • Let the checked exception propagate when the caller should decide what to do.
  • Handle the exception and provide a meaningful fallback when the method owns that decision.
  • Translate it into a domain-specific exception while preserving the original cause.
  • Return a result type that explicitly represents success and failure when that fits the API better.

The principle is simple: do not discard information about a failure unless the design explicitly calls for it.

19. Propagating or Converting an IOException

If a method cannot recover from an I/O failure, declaring throws IOException preserves the failure type and transfers the decision to its caller.


In some contexts, the API cannot expose a checked exception directly—for example, a method is used within an API that does not permit checked exceptions. Java provides UncheckedIOException for wrapping an IOException as an unchecked exception:


The original exception is passed as the cause. That matters because the cause contains the underlying details and stack trace that help explain the actual failure. Converting an exception should change how the failure is exposed, not erase why it happened.

Use this pattern when the API boundary justifies it. Do not wrap exceptions automatically merely to avoid writing a throws clause.

20. How Checked and Unchecked Exceptions Affect Method Contracts

A method signature is a contract between the method and its callers. A checked throws declaration makes a particular category of failure visible to the compiler. Callers must catch it or declare it, so the obligation travels through the call chain until a method handles it.

Unchecked exceptions do not create that compile-time obligation. They may still be documented in Javadoc, and their possible occurrence should still influence validation, tests, and API design. For example, a method can document that it throws IllegalArgumentException when an argument is negative, even though the compiler does not enforce handling.

A broad declaration such as throws Exception may be acceptable for a small demonstration, test, or deliberate pass-through method. In a public API, it is often less useful than declaring the specific checked exceptions callers should understand. A specific contract allows callers to distinguish between different failure cases and respond appropriately.

21. Exception Handling at Different Application Layers

A well-designed application usually assigns different responsibilities to different layers.

  • Low-level utilities often preserve and propagate failures because they do not know the application's recovery policy.
  • Service or domain layers may translate low-level errors into domain-specific exceptions when doing so adds useful meaning.
  • Application boundaries—such as command-line entry points, web controllers, or message consumers—often know how to report failures, select a response, or decide whether a task should be retried.
  • Process and framework boundaries may provide final logging or isolation, but they should not pretend that every failure can be safely recovered from.

This separation avoids duplicated handling logic. If every helper catches and suppresses errors, upper layers lose information. If every layer merely declares broad exceptions, callers may be burdened without gaining a useful contract. Good exception flow keeps failure information intact until a layer can make a meaningful decision.

22. E-Commerce Examples: Classification in Context

Invalid price text

Parsing "banana" as a number produces NumberFormatException, an unchecked exception. If the value came from a user, the application should validate it and show an input error. If trusted internal code unexpectedly generated it, the problem may indicate a defect.

Missing inventory file

Reading an inventory file can produce IOException, a checked exception. The application may retry, use a defined fallback, notify an operator, or stop the checkout process safely. The caller's choice is often important enough to expose explicitly.

Order ID not found

OrderNotFoundException may be checked or unchecked depending on the contract. A user-provided ID that does not exist can be an ordinary not-found outcome; an internally generated ID that unexpectedly disappears may indicate an invariant violation. Optional<Order> may be appropriate if absence is a normal result rather than an exceptional failure.

Insufficient stock

InventoryShortageException often represents a normal business outcome. The application may offer a smaller quantity, back-order the item, or stop the purchase. A checked exception can make this decision explicit, though a team may instead use unchecked exceptions with centralized handling if that better fits the architecture.

FailureExample typeLikely categoryDesign consideration
Invalid numeric textNumberFormatExceptionUncheckedValidate external input and report it clearly
File missing or unreadableIOExceptionCheckedCaller may retry, fall back, or stop
Order ID not foundOrderNotFoundExceptionEitherDepends on whether absence is normal or violates an invariant
Stock shortageInventoryShortageExceptionOften checkedCommon business outcome that checkout must address
Invalid internal lifecycle stateIllegalStateException or custom runtime exceptionUncheckedCorrect the state transition or calling logic

23. Best Practices for Checked and Unchecked Exceptions

  1. Use the hierarchy rule. Determine the category by inheritance: RuntimeException and Error branches are unchecked; other Exception subclasses are checked.
  2. Do not treat checked exceptions as automatically good. Use them when compile-time acknowledgment provides real value.
  3. Do not treat unchecked exceptions as permission to ignore failures. Document important conditions and handle them where a meaningful response exists.
  4. Avoid empty catch blocks. If an exception is intentionally ignored, explain why and ensure doing so is safe.
  5. Preserve causes when translating exceptions. Keep the original exception available for debugging and logging.
  6. Catch exceptions at the right layer. The layer that understands the recovery policy is usually the best place to handle the failure.
  7. Avoid overly broad exception contracts. Prefer specific checked exception types over throws Exception in public APIs when practical.
  8. Use validation for expected invalid input. Do not rely on catching exceptions as the only validation mechanism for routine input.
  9. Treat interruption deliberately. Do not silently swallow InterruptedException; propagate it or restore the interrupt status when appropriate.
  10. Do not normally recover from Error as though it were a business exception. Fix the underlying problem and let serious errors reach an appropriate process boundary.
  11. Keep custom exception policies consistent. Similar domain failures should follow a coherent design rule across the codebase.
  12. Document the caller's choices. An exception is most useful when developers understand what caused it and what responses are appropriate.

24. Common Misunderstandings

“Checked exceptions are the ones that happen at compile time.”

Incorrect. The compiler checks whether the code handles or declares them, but the exception itself generally occurs at runtime when the failing operation is executed.

“Unchecked exceptions cannot be caught.”

Incorrect. Unchecked exceptions can be caught. The distinction is that callers are not required by the compiler to catch or declare them.

“Every checked exception is recoverable.”

Not necessarily. Checked status describes compiler enforcement, not a guarantee that recovery is possible or wise.

“Every unchecked exception means the programmer made a mistake.”

Many unchecked exceptions reveal programming defects or invalid state, but APIs may also use them to represent domain failures handled at a central boundary. Their intended meaning depends on the API contract.

“Adding throws handles an exception.”

It does not handle the exception. It declares that the method may pass the exception to its caller. Handling requires code that responds to the failure, usually in a catch block or another explicit failure-handling mechanism.

“An Error is just another unchecked exception.”

Both are unchecked, but their roles differ. Error generally signals serious JVM or linkage problems and is not usually intended for routine application recovery.

25. Quick Revision

  • All Java exceptions and errors are rooted in Throwable.
  • Throwable has two direct branches: Exception and Error.
  • RuntimeException and all its subclasses are unchecked.
  • Error and all its subclasses are unchecked.
  • Other subclasses of Exception are checked.
  • A checked exception must be caught or declared if it can propagate out of a method.
  • An unchecked exception may be caught or declared, but the compiler does not require it.
  • Checked exceptions commonly represent external failures for which the caller may have a meaningful choice.
  • Unchecked exceptions commonly represent programming defects, invalid arguments, invalid states, or failures handled by a central boundary.
  • The superclass chosen for a custom exception determines its checked or unchecked status.
  • Avoid swallowing exceptions or returning ambiguous values that hide the original cause.
  • Preserve the cause when wrapping or translating exceptions.
  • The best exception design depends on the meaning of the failure and the responsibilities of the caller.

26. Conclusion

Checked and unchecked exceptions are two parts of Java's approach to making failures visible while keeping method contracts usable. Checked exceptions place a compile-time obligation on callers: they must handle the exception or declare that it may propagate. Unchecked exceptions remove that obligation, although they can still occur at runtime and may still need careful handling. Errors are also unchecked, but they normally represent serious runtime or environment problems rather than ordinary application failures.

The most reliable way to classify an exception is to inspect its position in the Throwable hierarchy. The most reliable way to choose a category for a custom exception is to consider the caller's real responsibilities. If the failure is an expected outcome and the caller has a meaningful recovery choice, a checked exception may be useful. If it indicates a programming bug, invalid state, or a failure handled consistently at a boundary, an unchecked exception may be more appropriate.

Neither category replaces thoughtful design. Preserve useful failure information, catch exceptions where a real decision can be made, document important behavior, and avoid turning an explicit failure into a silent or confusing secondary error. These practices make Java applications easier to maintain, debug, and evolve.