Java · lesson 18 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Error Types in Java
Understand Java's error types — compile errors, checked and unchecked exceptions, and Errors — with real stack traces showing how to read each one.
By SkillByExample · Updated
Java programs fail in three different ways: the compiler rejects the code, an exception
is thrown while it runs, or the JVM itself hits an Error it cannot recover from. Knowing which
one you are looking at tells you where the fix goes. Every example is a single file run with the
JDK 21 source launcher (java File.java).
The hierarchy
Everything that can be thrown extends Throwable:
Throwable
├── Error serious JVM problems — don't catch
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── IOException checked
├── SQLException checked
└── RuntimeException unchecked
├── NullPointerException
├── ArithmeticException
├── ArrayIndexOutOfBoundsException
├── ClassCastException
├── IllegalArgumentException
│ └── NumberFormatException
└── IllegalStateException
Checked means the compiler forces you to deal with it. Everything under RuntimeException
and Error is unchecked.
Compile errors happen before anything runs
public class CompileError {
public static void main(String[] args) {
System.out.println("this never prints");
int count = "three";
}
}
$ java CompileError.java
CompileError.java:4: error: incompatible types: String cannot be converted to int
int count = "three";
^
1 error
error: compilation failed
Even the first println didn’t run: the whole file must compile first. The message gives the file,
the line (4), and a caret under the offending expression.
Checked exceptions are enforced by the compiler
Calling a method that declares a checked exception without catching or declaring it is itself a compile error.
import java.nio.file.Files;
import java.nio.file.Path;
public class Checked {
public static void main(String[] args) {
String text = Files.readString(Path.of("config.txt"));
System.out.println(text);
}
}
$ java Checked.java
Checked.java:6: error: unreported exception IOException; must be caught or declared to be thrown
String text = Files.readString(Path.of("config.txt"));
^
1 error
error: compilation failed
Files.readString is declared throws IOException, so the compiler insists on a plan for a
missing file. Catching it gives one:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CheckedHandled {
public static void main(String[] args) {
try {
System.out.println(Files.readString(Path.of("config.txt")));
} catch (IOException e) {
System.out.println("Could not read: " + e);
}
}
}
$ java CheckedHandled.java
Could not read: java.nio.file.NoSuchFileException: config.txt
The exception that actually arrived is NoSuchFileException, a subclass of IOException, so the
catch (IOException e) block matched it. The alternative is to add throws IOException to
main and let the caller (here, the JVM) deal with it.
Unchecked exceptions: programming errors
The compiler doesn’t require you to handle RuntimeExceptions; they usually mean a bug. This
program triggers six common ones and prints each:
import java.util.List;
public class Unchecked {
public static void main(String[] args) {
Runnable[] bugs = {
() -> { String s = null; s.length(); },
() -> { int x = 10 / 0; },
() -> { int[] a = new int[3]; a[3] = 1; },
() -> { Object o = "text"; Integer i = (Integer) o; },
() -> Integer.parseInt("12a"),
() -> List.of(1, 2).iterator().remove(),
};
for (Runnable bug : bugs) {
try {
bug.run();
} catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
}
$ java Unchecked.java
NullPointerException: Cannot invoke "String.length()" because "<local0>" is null
ArithmeticException: / by zero
ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
NumberFormatException: For input string: "12a"
UnsupportedOperationException: null
All six compiled without complaint. Modern JDKs give “helpful” NullPointerException messages
naming what was null; <local0> appears instead of s because the source launcher compiles
without local-variable debug info. UnsupportedOperationException has no message at all, so getMessage()
returns null.
Reading a stack trace
An uncaught exception ends the program and prints the call stack, newest call first.
public class Trace {
public static void main(String[] args) {
System.out.println(average(new int[] {}));
}
static int average(int[] values) {
return sum(values) / values.length;
}
static int sum(int[] values) {
int total = 0;
for (int v : values) total += v;
return total;
}
}
$ java Trace.java
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Trace.average(Trace.java:7)
at Trace.main(Trace.java:3)
Read top-down: the exception and message, then where it was thrown (average, line 7), then who
called that (main, line 3). sum doesn’t appear because it had already returned. Dividing an
int by zero throws, but a double division by zero gives Infinity instead.
Error: the JVM itself is in trouble
Errors are unchecked too, but they signal conditions an application can’t fix, like running
out of stack or heap.
public class Overflow {
static int depth = 0;
static void recurse() {
depth++;
recurse(); // no base case
}
public static void main(String[] args) {
try {
recurse();
} catch (StackOverflowError e) {
System.out.println("StackOverflowError after roughly " + (depth / 1000) * 1000 + " calls (varies)");
Throwable t = e;
System.out.println("is an Exception? " + (t instanceof Exception));
}
}
}
$ java Overflow.java
StackOverflowError after roughly 20000 calls (varies)
is an Exception? false
The depth depends on the JVM’s stack size and the frame size, so your number will differ.
StackOverflowError is not an Exception, so a catch (Exception e) would not stop it. (The
compiler knows this too: written directly as e instanceof Exception, the test is rejected as
impossible, which is why it goes through a Throwable variable.) It is
caught here only to print the demonstration; real code fixes the missing base case.
Wrapped exceptions: “Caused by”
Libraries often wrap a low-level exception in a higher-level one. The original is kept as the
cause and printed after Caused by:.
public class Wrapped {
public static void main(String[] args) {
loadPort("http-port=eighty");
}
static int loadPort(String line) {
try {
return Integer.parseInt(line.split("=")[1]);
} catch (NumberFormatException e) {
throw new IllegalStateException("bad config line: " + line, e);
}
}
}
$ java Wrapped.java
Exception in thread "main" java.lang.IllegalStateException: bad config line: http-port=eighty
at Wrapped.loadPort(Wrapped.java:10)
at Wrapped.main(Wrapped.java:3)
Caused by: java.lang.NumberFormatException: For input string: "eighty"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at java.base/java.lang.Integer.parseInt(Integer.java:778)
at Wrapped.loadPort(Wrapped.java:8)
at Wrapped.main(Wrapped.java:3)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:484)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:208)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:135)
The top exception says what the program was doing; the cause says why it failed. The
jdk.compiler/...launcher.Main frames at the bottom are the source launcher calling your main;
they appear because this program was started with java Wrapped.java and are absent when you run
a compiled class.
Practice
- Without running it, predict whether each line compiles, and if it runs, which exception it throws. Then run the program.
Solution
public class Predict {
public static void main(String[] args) {
Object[] tries = new Object[3];
try { tries[0] = "abc".charAt(5); } catch (RuntimeException e) { System.out.println("1: " + e.getClass().getSimpleName()); }
try { tries[1] = Long.parseLong(" 42"); } catch (RuntimeException e) { System.out.println("2: " + e.getClass().getSimpleName()); }
try { tries[2] = ((Object) 3.0) instanceof Integer i ? i : (Integer) (Object) 3.0; } catch (RuntimeException e) { System.out.println("3: " + e.getClass().getSimpleName()); }
System.out.println(10.0 / 0);
}
}
$ java Predict.java
1: StringIndexOutOfBoundsException
2: NumberFormatException
3: ClassCastException
Infinity
All four lines compile — unchecked exceptions are only discovered at run time. parseLong
rejects the leading space, a Double can’t be cast to Integer, and floating-point division by
zero is not an error at all.
- Fix
Trace.javasoaverageof an empty array returns 0 instead of crashing.
Solution
public class TraceFixed {
public static void main(String[] args) {
System.out.println(average(new int[] {}));
System.out.println(average(new int[] {2, 4, 9}));
}
static int average(int[] values) {
if (values.length == 0) return 0;
int total = 0;
for (int v : values) total += v;
return total / values.length;
}
}
$ java TraceFixed.java
0
5
A guard clause prevents the bug instead of catching ArithmeticException after the fact. (Note
the integer average of 2, 4, 9 is 5, not 5.0.)
- Make
Checked.javacompile by declaring the exception instead of catching it. What does the output look like when the file is missing?
Solution
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Declared {
public static void main(String[] args) throws IOException {
System.out.println(Files.readString(Path.of("config.txt")));
}
}
$ java Declared.java
Exception in thread "main" java.nio.file.NoSuchFileException: config.txt
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:261)
at java.base/java.nio.file.Files.newByteChannel(Files.java:380)
at java.base/java.nio.file.Files.newByteChannel(Files.java:432)
at java.base/java.nio.file.Files.readAllBytes(Files.java:3281)
at java.base/java.nio.file.Files.readString(Files.java:3359)
at java.base/java.nio.file.Files.readString(Files.java:3318)
at Declared.main(Declared.java:7)
throws passes the responsibility up; from main it reaches the JVM, which prints the trace and
exits. The JDK frames (sun.nio.fs...) vary by operating system — on Windows they mention
WindowsException — but the last line still points at your code.
- Add a
Caused bychain two levels deep: wrap theIllegalStateExceptionfromWrappedin aRuntimeException("startup failed", e)and catch it inmain, printing each cause’s message.
Solution
public class Chain {
public static void main(String[] args) {
try {
start();
} catch (RuntimeException e) {
for (Throwable t = e; t != null; t = t.getCause()) {
System.out.println(t.getClass().getSimpleName() + ": " + t.getMessage());
}
}
}
static void start() {
try {
Integer.parseInt("eighty");
} catch (NumberFormatException e) {
try {
throw new IllegalStateException("bad config", e);
} catch (IllegalStateException ise) {
throw new RuntimeException("startup failed", ise);
}
}
}
}
$ java Chain.java
RuntimeException: startup failed
IllegalStateException: bad config
NumberFormatException: For input string: "eighty"
getCause() walks from the outermost wrapper down to the root cause, which returns null for its
own cause and ends the loop.