Java errors are problems that stop your code from compiling, crashing it while running, or making it produce the wrong output. They fall into three main groups: compile-time errors (caught by the compiler before the program runs), runtime errors (thrown by the JVM during execution), and logic errors (the code runs fine but gives wrong results). Most beginner errors in Java are runtime exceptions like NullPointerException, ArrayIndexOutOfBoundsException, and NumberFormatException.
If you are a student learning Java, you will run into these errors a lot. That is normal. Every working coder has stared at the same red text in their IDE at some point. The good news is that almost every common Java error has a clear cause and a clean fix. Once you know what each error means and how it shows up in your code, you can spot the problem in seconds instead of hours.
This guide covers the 8 Java errors students run into most in homework and projects. For each one you get a plain explanation, real code, and the exact fix. You will also learn how to throw your own exceptions and how to find any bug with a debugger. If you are stuck on a tricky bug in your assignment, our Java homework help service can help.
In this guide:
- Compile-time vs runtime vs logic errors
- NullPointerException
- ArrayIndexOutOfBoundsException
- NumberFormatException
- InputMismatchException
- ClassCastException
- ConcurrentModificationException
- StackOverflowError
- ArithmeticException
- How to throw your own exceptions
- How to debug Java errors step by step
- Frequently asked questions
Compile-Time vs Runtime vs Logic Errors
Before we look at specific errors, you need to understand the three big buckets. Knowing which type of error you are dealing with tells you where to look for the fix.
A compile-time error happens before your program even starts running. The Java compiler scans your code, finds a problem like a missing semicolon or a wrong variable name, and refuses to build the .class file. These errors are the easiest to fix because the compiler points right at the line.
A runtime error sneaks past the compiler. Your code looks fine on paper, so the JVM starts running it. But somewhere during execution, the program tries to do something illegal, like divide by zero or open a file that does not exist. The JVM throws an exception and your program crashes unless you catch it.
A logic error is the trickiest type. The code compiles, runs all the way through, and gives you an answer. The problem is that the answer is wrong. Maybe you used + instead of -, or your loop runs one time too many. The compiler cannot help you here. You have to test, trace, and debug yourself.
Here is a quick table that lays out the differences:
| Feature | Compile-Time Error | Runtime Error | Logic Error |
|---|---|---|---|
| When it happens | Before the code runs | While the code is running | After the code finishes |
| Who catches it | Java compiler | JVM | You, by testing |
| Common cause | Bad syntax, typos, missing brackets | Bad input, null values, bad math | Wrong formula or condition |
| Example | Missing ; at end of line | NullPointerException | Reversing a number with / instead of % |
| Difficulty to fix | Easy | Medium | Hard |
| Stops the program? | Yes, will not compile | Yes, crashes mid-run | No, runs but gives wrong output |
Most of the errors covered in this guide are runtime errors, since those are the ones that show up after you think your code is working.
8 Common Java Errors and How to Fix Each One
Each section below shows the error in real code, explains what causes it, and gives you fixes you can use in your assignment right away.
NullPointerException
This is the single most common Java error you will ever see. A NullPointerException (often shortened to NPE) happens when you try to use an object that has not been given a value yet. In Java, an object variable that has no value is called null. If you call a method on a null object or try to read its fields, the JVM throws a NullPointerException and your program crashes.
Code Example:
public class NullExample {
public static void main(String[] args) {
String name = null;
System.out.println(name.length()); // NullPointerException here
}
}
In the code above, the variable name was set to null. When we call name.length(), there is no actual String object to work with, so the JVM throws the error.
Fix 1: Check for null before using the object
The simplest fix is to add an if check before you call any method.
String name = null;
if (name != null) {
System.out.println(name.length());
} else {
System.out.println("Name is empty");
}
Fix 2: Initialize the variable with a real value
If your variable should always have a value, just give it one when you declare it.
String name = "CodingZap";
System.out.println(name.length()); // Works fine, prints 9
Fix 3: Use Optional for safer code
Java 8 added the Optional class, which forces you to think about whether a value might be missing.
import java.util.Optional;
public class OptionalExample {
static String getName() {
return null; // pretend this value came from user input or a database
}
public static void main(String[] args) {
Optional<String> name = Optional.ofNullable(getName());
name.ifPresent(n -> System.out.println(n.length())); // only runs if there is a value
System.out.println(name.orElse("No name given")); // No name given
}
}
A good habit is to never trust outside data. If a method returns an object, ask yourself whether that method could return null. If yes, check it first.
ArrayIndexOutOfBoundsException
This error shows up when you try to read or write a position in an array that does not exist. In Java, arrays are numbered starting at 0. So if you create an array of size 5, the valid positions are 0, 1, 2, 3, and 4. Trying to use position 5 or higher, or any negative number, throws an ArrayIndexOutOfBoundsException.
Code Example:
public class ArrayExample {
public static void main(String[] args) {
int[] nums = {10, 20, 30, 40, 50};
System.out.println(nums[5]); // ArrayIndexOutOfBoundsException
}
}
The array nums has five elements at positions 0 through 4. When we ask for nums[5], the JVM throws the error because that position does not exist.
Fix 1: Use array.length in your loops
When you loop through an array, always use the length property instead of a hardcoded number.
int[] nums = {10, 20, 30, 40, 50};
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
Fix 2: Check the index before access
If the index comes from user input or another method, check it first.
int index = 5;
if (index >= 0 && index < nums.length) {
System.out.println(nums[index]);
} else {
System.out.println("Index is out of range");
}
Fix 3: Use a for-each loop when you do not need the index
If you just want to read each value, the for-each loop skips the index problem entirely.
for (int n : nums) {
System.out.println(n);
}
A common mistake students make is writing i <= nums.length instead of i < nums.length. That single character difference causes the error every time.
NumberFormatException
A NumberFormatException is a runtime error that happens when you try to turn a String into a number, but the String does not actually hold a valid number. This is one of the most common errors when you take input from a user and forget to clean it up first.
Code Example:
public class NumberExample {
public static void main(String[] args) {
String input = "one";
int number = Integer.parseInt(input); // NumberFormatException
System.out.println(number);
}
}
Here, the String "one" is a word, not a digit. When Integer.parseInt() tries to convert it, the JVM throws the error.
Common causes you should watch for:
- Words mixed with numbers, like
"45AB"or"one1" - Empty strings
""or strings with only spaces" " - A
nullvalue passed toparseInt() - Numbers too big for the data type, like
"999999999999"for anint - Wrong radix base when parsing binary or hexadecimal values
Fix 1: Print the input first to see what you are working with
String input = " 123 ";
System.out.println("Raw input: [" + input + "]");
int number = Integer.parseInt(input); // Still fails because of spaces
When you print the raw input, you can see if there are hidden spaces or weird characters.
Fix 2: Use trim() to remove extra spaces
String input = " 123 ";
input = input.trim();
int number = Integer.parseInt(input);
System.out.println("Number: " + number); // Works, prints 123
The trim() method strips spaces from both ends of the String.
Fix 3: Wrap parsing in a try-catch block
This is the safest method, because it catches the error no matter what the bad input was.
String input = "twoAB";
try {
int number = Integer.parseInt(input);
System.out.println("Number: " + number);
} catch (NumberFormatException e) {
System.out.println("That is not a valid number: " + input);
}
When the bad input comes in, the catch block runs instead of crashing the program. This is the method most pro coders use in production code.
Fix 4: Use a bigger type when the number is too large
An int can only hold values up to 2,147,483,647. If the String holds a bigger number, like a long ID, Integer.parseInt() fails even though every character is a digit. Switch to Long.parseLong(). For numbers too big for a long, use BigInteger.
public class BigNumberExample {
public static void main(String[] args) {
String big = "9999999999"; // bigger than the int limit of 2147483647
// int number = Integer.parseInt(big); // this line would throw NumberFormatException
long number = Long.parseLong(big); // a long holds much bigger values
System.out.println(number); // 9999999999
}
}
Fix 5: Tell Java the base for binary or hex strings
Integer.parseInt() reads base 10 by default. A hex value like "45AB" fails because A and B are not decimal digits. Pass the base as a second argument and the same String converts fine.
public class RadixExample {
public static void main(String[] args) {
String hex = "45AB";
// Integer.parseInt(hex) reads base 10, so "A" and "B" are not allowed
int value = Integer.parseInt(hex, 16); // tell Java the string is hexadecimal
System.out.println(value); // 17835
int binary = Integer.parseInt("1011", 2); // base 2
System.out.println(binary); // 11
}
}
Fix 6: Write one helper method and reuse it
If your assignment reads numbers in several places, put the trim and the try-catch in one small method. It returns a default value instead of crashing, and the rest of your code stays clean.
public class SafeParse {
// Returns the number, or the fallback value if the text is not a valid int
static int parseIntOrDefault(String text, int fallback) {
if (text == null) {
return fallback;
}
try {
return Integer.parseInt(text.trim());
} catch (NumberFormatException e) {
return fallback;
}
}
public static void main(String[] args) {
System.out.println(parseIntOrDefault(" 42 ", 0)); // 42
System.out.println(parseIntOrDefault("forty", 0)); // 0
System.out.println(parseIntOrDefault(null, -1)); // -1
}
}
Real homework example: averaging test scores
Say your assignment gives you a line of scores separated by commas and asks for the average. A lot of first attempts look like this:
public class AverageScores {
public static void main(String[] args) {
String line = "85, 92, absent, 78";
String[] parts = line.split(",");
int total = 0;
for (String part : parts) {
total += Integer.parseInt(part); // crashes on " 92"
}
System.out.println("Average: " + (total / parts.length));
}
}
This crashes on the second score, not the third. After split(","), the pieces are "85", " 92", " absent", and " 78". The leading space in " 92" is enough to throw NumberFormatException: For input string: " 92". Here is the fixed version:
public class AverageScores {
public static void main(String[] args) {
String line = "85, 92, absent, 78";
String[] parts = line.split(",");
int total = 0;
int count = 0;
for (String part : parts) {
String cleaned = part.trim();
try {
total += Integer.parseInt(cleaned);
count++;
} catch (NumberFormatException e) {
System.out.println("Skipping \"" + cleaned + "\" because it is not a number");
}
}
if (count > 0) {
System.out.println("Average: " + (double) total / count);
}
}
}
Three things changed. Each piece is trimmed before parsing. A bad value is skipped with a message instead of crashing the program. And the average divides by count as a double, so a skipped value does not throw off the math. The output is Skipping "absent" because it is not a number followed by Average: 85.0. If trim() is new to you, our guide on removing whitespace from a string in Java walks through it.
InputMismatchException
This one is a close cousin of NumberFormatException, and it shows up in almost every intro Java class that uses Scanner. You call nextInt() or nextDouble(), the user types a word, and Scanner throws InputMismatchException because the next piece of input does not match the type you asked for.
Code Example:
import java.util.Scanner;
public class AgeInput {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = sc.nextInt(); // typing "twenty" throws InputMismatchException
System.out.println("Next year you will be " + (age + 1));
}
}
Type twenty at the prompt and the program stops with java.util.InputMismatchException.
Fix: Check with hasNextInt() before you read
Ask Scanner whether the next input is a whole number before you read it. If it is not, read it as a String with next() so Scanner moves past it, then ask again.
import java.util.Scanner;
public class AgeInput {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Enter your age: ");
while (!sc.hasNextInt()) {
String bad = sc.next(); // read the bad word so Scanner moves past it
System.out.print("\"" + bad + "\" is not a whole number. Try again: ");
}
int age = sc.nextInt();
System.out.println("Next year you will be " + (age + 1));
}
}
Watch out for one trap. Some students catch InputMismatchException inside a loop but never clear the bad input. Scanner does not skip the word that failed, so nextInt() fails on the same word again and the loop never ends. Whether you use hasNextInt() or try-catch, always call next() or nextLine() to throw the bad input away.
ClassCastException
A ClassCastException is thrown when you try to force an object to be a type it is not. This usually happens with collections or when you cast between parent and child classes in inheritance.
Code Example:
public class CastExample {
public static void main(String[] args) {
Object obj = "Hello";
Integer num = (Integer) obj; // ClassCastException
System.out.println(num);
}
}
The variable obj holds a String, but we try to cast it to an Integer. Since a String is not an Integer, the JVM throws the error.
Fix 1: Use instanceof to check the type first
Object obj = "Hello";
if (obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.println(num);
} else {
System.out.println("Object is not an Integer");
}
The instanceof operator tells you the real type of the object before you try to cast.
Fix 2: Use pattern matching (Java 16 and above)
Newer versions of Java let you check and cast in one step.
Object obj = "Hello";
if (obj instanceof String s) {
System.out.println("Length is: " + s.length());
}
Fix 3: Use generics to avoid casting altogether
If you are working with collections like ArrayList, always use generics. They lock in the type at compile time so you never need to cast.
// Bad: raw type, will need casting
List rawList = new ArrayList();
rawList.add("Hello");
String s = (String) rawList.get(0); // Risky
// Good: generic type, no casting needed
List<String> safeList = new ArrayList<>();
safeList.add("Hello");
String safe = safeList.get(0); // No cast, no risk
Generics are one of the easiest ways to write safer Java code as a student. Get in the habit of using them on day one.
ConcurrentModificationException
This scary-sounding error pops up when you change the structure of a collection (like an ArrayList or HashSet) while you are looping through it. Java does not let you add or remove items from a collection during iteration, because it confuses the iterator about the collection's size.
Code Example:
import java.util.*;
public class ConcurrentExample {
public static void main(String[] args) {
List<String> langs = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
for (String l : langs) {
if (l.startsWith("J")) {
langs.remove(l); // ConcurrentModificationException
}
}
}
}
The for-each loop is reading the list at the same time we are removing items from it. Java spots this and throws the error to stop a worse bug from happening.
Common causes:
- Removing items from a list inside a for-each loop
- Adding new items to the list during iteration
- Changing the list without going through an iterator
Fix 1: Use the Iterator's remove() method
When you need to remove items during iteration, use an Iterator and its remove() method.
import java.util.*;
List<String> langs = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
Iterator<String> it = langs.iterator();
while (it.hasNext()) {
String l = it.next();
if (l.startsWith("J")) {
it.remove(); // Safe removal
}
}
System.out.println(langs); // [Python, C++]
The iterator knows it is removing an element, so it adjusts the count properly.
Fix 2: Make changes on a separate copy
Loop through the original list, but make changes on a copy.
List<Integer> nums = new ArrayList<>(Arrays.asList(10, 20, 30));
List<Integer> copy = new ArrayList<>(nums);
for (int n : nums) {
if (n == 20) {
copy.add(40);
}
}
System.out.println(copy); // [10, 20, 30, 40]
Fix 3: Use CopyOnWriteArrayList for thread-safe code
Java has a special list type called CopyOnWriteArrayList that allows safe changes during iteration. It is mostly used in multi-threaded code, but it works here too.
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
List<String> code = new CopyOnWriteArrayList<>(Arrays.asList("Coding", "Zap", "One"));
for (String c : code) {
if (c.equals("One")) {
code.remove(c); // Safe with CopyOnWriteArrayList
}
}
System.out.println(code);
This list keeps a separate snapshot for the iterator, so changes do not break the loop. The trade-off is that it is slower than ArrayList, so use it only when you really need it.
Fix 4: Use removeIf() (the shortest fix)
Since Java 8, every list and set has a removeIf() method. You give it a condition, and it removes every matching item safely. For most homework, this is the cleanest answer.
import java.util.*;
public class RemoveIfExample {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(Arrays.asList(10, 15, 20, 35));
nums.removeIf(n -> n % 2 == 0); // removes 10 and 20 safely
System.out.println(nums); // [15, 35]
}
}
Fix 5: Loop backwards with an index
If your course has not covered lambdas yet, a plain for loop that runs from the end of the list to the start works too. Removing an item only shifts the items after it, and you have already checked those.
import java.util.*;
public class RemoveBackwards {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(Arrays.asList(10, 15, 20, 35));
for (int i = nums.size() - 1; i >= 0; i--) {
if (nums.get(i) % 2 == 0) {
nums.remove(i); // remove by position, starting from the end
}
}
System.out.println(nums); // [15, 35]
}
}
It happens with maps too
Removing entries from a HashMap while you loop over keySet() or entrySet() throws the same exception. Call removeIf() on the entry set instead.
import java.util.*;
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("Ana", 91);
scores.put("Ben", 48);
scores.put("Cara", 73);
// Wrong: calling scores.remove(name) inside a loop over scores.keySet()
// Right: let the map remove matching entries for you
scores.entrySet().removeIf(entry -> entry.getValue() < 50);
System.out.println(scores); // {Cara=73, Ana=91} (a HashMap does not keep insertion order)
}
}
Real homework example: removing failing scores
A common assignment asks you to take a list of scores and remove every score below 50. This version looks right but crashes:
import java.util.*;
public class RemoveFailing {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(Arrays.asList(88, 42, 67, 39, 95));
for (Integer s : scores) {
if (s < 50) {
scores.remove(s); // ConcurrentModificationException
}
}
System.out.println(scores);
}
}
It fails on the loop step right after 42 is removed, because the list changed size under the for-each loop. Notice that s is declared as Integer, not int. That matters. With an Integer, scores.remove(s) removes that value. With an int, it removes the item at that position instead, so remove(42) crashes with IndexOutOfBoundsException. Here is the fix:
import java.util.*;
public class RemoveFailing {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(Arrays.asList(88, 42, 67, 39, 95));
scores.removeIf(s -> s < 50);
System.out.println(scores); // [88, 67, 95]
}
}
StackOverflowError
This error is a favorite of recursion problems. A StackOverflowError happens when your program's call stack runs out of memory. The call stack is where Java keeps track of every method that has been called but has not finished yet. When you have a recursive method that never stops, the stack keeps growing until it overflows.
Code Example:
public class StackExample {
public static int factorial(int n) {
return n * factorial(n - 1); // No base case, runs forever
}
public static void main(String[] args) {
factorial(5); // StackOverflowError
}
}
The factorial method calls itself with no end in sight. After a few thousand calls, the stack fills up and the JVM crashes.
Common causes:
- Missing a base case in a recursive method
- Writing a base case that never gets reached
- Two methods that call each other in a loop (mutual recursion with no exit)
Fix 1: Add a base case
Every recursive method needs a clear stopping point. For factorial, that point is when n reaches 0 or 1.
public static int factorial(int n) {
if (n <= 1) {
return 1; // Base case: stops at 0 or 1
}
return n * factorial(n - 1);
}
Now when n hits 0, the method returns a real number instead of calling itself again.
Fix 2: Use print statements to trace the recursion
When you cannot tell why your recursion is not stopping, add a print line at the top of the method.
public static int factorial(int n) {
System.out.println("factorial called with: " + n);
if (n == 0) return 1;
return n * factorial(n - 1);
}
If you see numbers going negative or never reaching the base case, you know your logic is off.
Fix 3: Read the stack trace
The error message Java prints shows you the exact line where the recursion is looping. Look at the top of the stack trace and check the base case in that method first.
Fix 4: Increase the stack size (last resort)
If your recursion is correct but just very deep, you can give the JVM more stack space with a command line flag:
java -Xss2m YourProgram
This sets the stack size to 2 megabytes per thread. Use this only after you have made sure your base case is right. Bigger stack size does not fix bad code, it only delays the crash.
Why the stack runs out
Every time a method is called, Java adds a stack frame to the call stack. The frame holds that call's parameters and local variables. When the method returns, its frame is removed. A recursive method that never returns keeps adding frames until the stack is full. The stack has a fixed size set by the JVM, commonly 1 MB on 64-bit systems, so the crash usually comes after thousands of calls.
A cause students miss: a base case that is never reached
Having a base case is not enough. The recursion has to actually reach it. This version works for 5 but crashes for a negative number, because counting down from -3 never lands on 0:
public class FactorialBug {
static long factorial(int n) {
if (n == 0) { // only stops at exactly 0
return 1;
}
return n * factorial(n - 1);
}
public static void main(String[] args) {
System.out.println(factorial(5)); // 120
System.out.println(factorial(-3)); // -3, -4, -5... never reaches 0
}
}
Fix it in two ways. Use n <= 1 so the base case covers more than one exact value, and reject input that makes no sense by throwing an exception. You will see how throw works further down this guide.
public class FactorialFixed {
static long factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be 0 or more, got " + n);
}
if (n <= 1) { // covers both 0 and 1
return 1;
}
return n * factorial(n - 1);
}
public static void main(String[] args) {
System.out.println(factorial(5)); // 120
System.out.println(factorial(-3)); // clear error message instead of a stack overflow
}
}
Two methods that call each other
Recursion can also be indirect. Here isEven() calls isOdd(), which calls isEven() again. Without the n == 0 checks, the two methods would bounce back and forth until the stack overflows. Every method in the cycle needs its own way out.
public class EvenOdd {
static boolean isEven(int n) {
if (n == 0) {
return true; // way out for isEven
}
return isOdd(n - 1);
}
static boolean isOdd(int n) {
if (n == 0) {
return false; // way out for isOdd
}
return isEven(n - 1);
}
public static void main(String[] args) {
System.out.println(isEven(10)); // true
System.out.println(isOdd(7)); // true
}
}
Fix 5: Use a loop when the recursion is too deep
If your logic is right but the input is simply large, a loop is the safer choice. It uses one stack frame no matter how big n gets. Most recursive solutions for factorial, sums, or Fibonacci can be rewritten this way. For more on how recursion works in sorting, see our guide to recursive sorting algorithms.
public class FactorialLoop {
static long factorial(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
public static void main(String[] args) {
System.out.println(factorial(20)); // 2432902008176640000
}
}
ArithmeticException
The ArithmeticException is thrown when something goes wrong in a math operation. The most famous trigger is dividing by zero with integers.
Code Example:
public class MathExample {
public static void main(String[] args) {
int a = 10;
int b = 0;
int result = a / b; // ArithmeticException: / by zero
System.out.println(result);
}
}
When Java sees 10 / 0 with integers, it throws the error immediately.
One interesting note: this only happens with integer division. If you divide a double by zero, Java gives you Infinity or NaN instead of an error.
double a = 10.0;
double b = 0.0;
System.out.println(a / b); // Prints Infinity, no error
Fix 1: Check the divisor before dividing
int a = 10;
int b = 0;
if (b != 0) {
System.out.println(a / b);
} else {
System.out.println("Cannot divide by zero");
}
Fix 2: Use try-catch
try {
int result = a / b;
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("Math error: " + e.getMessage());
}
The fix is almost always to validate the input before doing the math. Trust no number that comes from outside your method.
How to Throw Your Own Exceptions in Java
So far you have seen exceptions that Java throws at you. Many assignments also ask you to throw your own. A typical rubric line reads "reject invalid input" or "throw an exception if the balance is too low." To do that well, you need to know the two kinds of exceptions and the difference between throw and throws.
Checked vs unchecked exceptions
Java sorts exceptions into two groups, and the compiler treats them differently.
| Checked | Unchecked | |
|---|---|---|
| Does the compiler make you handle it? | Yes. You must catch it or declare it with throws. | No. Your code compiles either way. |
| What it extends | Exception (but not RuntimeException) | RuntimeException |
| What it usually means | Something outside your code failed, like a missing file | A bug or bad input inside your code |
| Examples | IOException, FileNotFoundException, InterruptedException | NullPointerException, NumberFormatException, IllegalArgumentException |
Every runtime exception earlier in this guide is unchecked. That is why your code compiles and then crashes later. StackOverflowError is a third kind, an Error, which you are not expected to catch at all.
Use throw to reject bad input
The throw keyword stops the method right away and hands an exception to whoever called it. Use it when your method gets a value it cannot work with. Always include a message that says what went wrong, because that message is what shows up in the stack trace.
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be more than 0, got " + amount);
}
balance += amount;
}
public double getBalance() {
return balance;
}
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(100);
System.out.println("Balance: " + account.getBalance());
account.deposit(-20); // throws IllegalArgumentException with your message
}
}
The deposit of 100 works. The deposit of -20 stops the program with IllegalArgumentException: Deposit must be more than 0, got -20.0. That is exactly what you want. The bad value is caught on the line where it came in, not three methods later when the balance looks wrong.
Use throws to pass a checked exception to the caller
The throws keyword goes in the method signature, not the body. It tells callers that this method can fail with a checked exception, so they have to decide what to do. The compiler then makes every caller catch it or declare it too. If method signatures are new to you, our guide on method signatures in Java explains each part.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ReadScores {
// "throws" tells callers this method can fail with a checked exception
static int countLines(String fileName) throws FileNotFoundException {
int lines = 0;
try (Scanner sc = new Scanner(new File(fileName))) {
while (sc.hasNextLine()) {
sc.nextLine();
lines++;
}
}
return lines;
}
public static void main(String[] args) {
try {
System.out.println("Lines: " + countLines("scores.txt"));
} catch (FileNotFoundException e) {
System.out.println("Could not find the file: " + e.getMessage());
}
}
}
If scores.txt is missing, the program prints Could not find the file: scores.txt plus the reason, instead of crashing. The try (...) part is called try-with-resources. It closes the Scanner for you, even when something fails, so you never leave a file open.
throw vs throws at a glance
throw | throws | |
|---|---|---|
| Where it goes | Inside a method body | In the method signature |
| What it does | Throws one exception right now | Warns callers the method might throw these exceptions |
| What follows it | An exception object, like throw new IllegalArgumentException("...") | Exception class names, like throws IOException |
| How many at once | One | Several, separated by commas |
| Typical use | Rejecting bad input in your own code | Passing checked exceptions, like file errors, up to the caller |
Create your own exception class
Sometimes no built-in exception describes the problem well. You can make your own by extending Exception for a checked exception or RuntimeException for an unchecked one. Banking and inventory assignments use this a lot.
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
public class Wallet {
private double balance = 50;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Cannot withdraw " + amount + ", balance is only " + balance);
}
balance -= amount;
}
public static void main(String[] args) {
Wallet wallet = new Wallet();
try {
wallet.withdraw(20); // works, balance is now 30
wallet.withdraw(100); // fails
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
The first withdrawal of 20 works and leaves 30. The second one fails, and the catch block prints Error: Cannot withdraw 100.0, balance is only 30.0. Because InsufficientFundsException extends Exception, it is checked. So withdraw() has to declare it with throws, and main has to catch it.
Mistakes that cost points
- Empty catch blocks.
catch (Exception e) { }hides the error. Your program keeps going with bad data, and you lose the stack trace that would have told you why. - Catching Exception everywhere. Catch the specific type you expect, like
NumberFormatException. A broad catch also swallows bugs you never planned for. - Throwing without a message.
throw new IllegalArgumentException()tells the reader nothing. Say what the bad value was. - Adding throws Exception to main just to quiet the compiler. It compiles, but the first missing file crashes your program with a raw stack trace. Handle the checked exception where you can do something useful about it.
- Using exceptions for normal decisions. If you can check a condition with an
if, do that. Exceptions are for cases that should not happen.
How to Debug Java Errors Step by Step
Knowing the errors is half the battle. The other half is having a steady method to track them down when they show up. Here is a step-by-step process you can follow every time your code breaks.
Step 1: Read the error message carefully
The very first thing Java prints when something breaks is gold. It tells you the name of the error (like NullPointerException), a short message about what went wrong, and the line number where it happened. Most students glance at the error and panic. Slow down. Read it twice.
Step 2: Look at the stack trace
Below the error message, Java prints a list of method calls that led to the crash. This is called the stack trace. The top line is where the error actually happened. The lines below show how the program got there. Start at the top and work down.
Step 3: Open the file and find the line
Jump to the line number in your code. Look at the variables on that line. Ask yourself: which one could be null, which index could be out of range, which value could be wrong? Most of the time, the answer is right there.
Step 4: Use print statements
When the error is not obvious, add print statements before the broken line. Print the values of every variable. This often reveals that a variable holds something you did not expect.
System.out.println("name = [" + name + "]");
System.out.println("index = " + index);
System.out.println("list size = " + list.size());
The square brackets help you spot hidden spaces or empty strings.
Step 5: Use a debugger
Every Java IDE (IntelliJ, Eclipse, VS Code) has a built-in debugger. Set a breakpoint on the broken line, run the program in debug mode, and inspect every variable at that exact moment. This is much faster than print statements once you get the hang of it.
Three kinds of breakpoints are worth knowing:
- Line breakpoint. Click in the margin next to a line number. The program pauses right before that line runs.
- Conditional breakpoint. Right-click a breakpoint and add a condition, like
i == 49. The program only pauses when the condition is true, which saves you from clicking through 49 loop passes. - Exception breakpoint. The debugger pauses the moment a chosen exception is thrown, like
NullPointerException, with every variable still visible. In IntelliJ, open Run > View Breakpoints and add a Java Exception Breakpoint. In Eclipse, use Run > Add Java Exception Breakpoint.
Once the program pauses, you move through the code one step at a time. These are the default shortcuts on Windows and Linux. Mac keys are different.
| Action | IntelliJ IDEA | Eclipse | VS Code |
|---|---|---|---|
| Start debugging | Shift+F9 | F11 | F5 |
| Step over (run this line) | F8 | F6 | F10 |
| Step into (enter the method) | F7 | F5 | F11 |
| Step out (finish this method) | Shift+F8 | F7 | Shift+F11 |
| Resume to the next breakpoint | F9 | F8 | F5 |
Step 6: Search the error message
If you are still stuck, copy the error name and a key part of the message, then paste it into Google or Stack Overflow. Other students and pros have hit the same problem, and you will almost always find an answer in the first three results.
Step 7: Reduce the code to the smallest version that breaks
If your program is big, copy the broken section into a tiny new test program. Sometimes the act of cutting away unrelated code makes the bug obvious.
Following these steps in order will save you hours every week. Most experienced coders use the same routine.
Log instead of print in bigger programs
Print statements work, but you have to delete them all before you submit. Java's built-in logger does the same job and can be switched off in one line. No extra library is needed.
import java.util.logging.Logger;
public class GradeCalculator {
private static final Logger LOG = Logger.getLogger(GradeCalculator.class.getName());
static char letterGrade(int score) {
LOG.info("letterGrade called with score = " + score);
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
return 'F';
}
public static void main(String[] args) {
System.out.println(letterGrade(84)); // B
}
}
Each call prints an INFO line with a timestamp, and then your normal output. When you are done debugging, add LOG.setLevel(Level.WARNING); at the start of main (and import java.util.logging.Level). The info lines disappear and the rest of your code stays the same.
Turn the bug into a test
Once you find a bug, write a small JUnit test that fails because of it. Fix the code, and the test passes. If a later change brings the bug back, the test catches it before your grader does. Here are three tests for the parseIntOrDefault() helper from the NumberFormatException section:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class SafeParseTest {
@Test
void parsesNumberWithSpaces() {
assertEquals(42, SafeParse.parseIntOrDefault(" 42 ", 0));
}
@Test
void returnsFallbackForWords() {
assertEquals(0, SafeParse.parseIntOrDefault("forty", 0));
}
@Test
void parseIntThrowsOnWords() {
assertThrows(NumberFormatException.class, () -> Integer.parseInt("forty"));
}
}
assertThrows() checks that a piece of code throws the exception you expect. It is the simplest way to prove your error handling works, and many Java autograders run JUnit tests just like these.
Best Practices to Prevent Java Errors
Catching errors is good. Avoiding them in the first place is better. Here are habits that will keep most of these errors out of your code from the start.
Initialize variables when you declare them. Do not leave object variables as null if you can give them a real value right away. This kills most NullPointerException issues before they happen.
Always use generics with collections. Write List<String> instead of List. The compiler will catch type mistakes early and you will never need to cast.
Use try-catch around risky code. Anything that takes outside input, opens a file, or parses data should be inside a try-catch block. Even a simple log message in the catch helps you spot bugs faster.
Check array and list bounds. Before you use an index, make sure it is between 0 and length - 1. A two-second check saves a long debugging session.
Write base cases first in recursion. When you start a recursive method, type the base case before the recursive call. That way you can never forget it.
Use meaningful variable names. A variable called userInputString is clearer than s. Good names make errors easier to spot during code review.
Test with edge cases. Run your code with empty strings, zero values, negative numbers, and null inputs. If it breaks on these, fix it before submitting.
Keep methods short. A method that does one thing in 10 lines is easier to debug than one that does five things in 100 lines.
Use final for values that should not change. Marking a variable as final tells the compiler (and other readers) that it should not be reassigned.
Building these habits early makes coding more fun, because you spend less time stuck on red error messages and more time building things that work.
Frequently Asked Questions
1. What is the most common Java error for beginners?
The NullPointerException is by far the most common Java error students face. It happens when you call a method on an object that has not been given a value yet. Always check if an object is null before using it.
2. What is the difference between a Java error and a Java exception?
An Error in Java usually means something serious went wrong with the JVM itself, like running out of memory or stack space. An Exception is a problem in your code that you can usually catch and recover from. StackOverflowError is an Error, while NullPointerException is an Exception.
3. How do I find the line where my Java error happened?
Look at the stack trace that Java prints. The top line shows the file name and the exact line number where the error was thrown. Open that file, jump to that line, and start checking the variables there.
4. Can I prevent NullPointerException without if checks everywhere?
Yes. Use the Optional class from Java 8, or annotations like @NonNull from libraries like Lombok. You can also follow the rule of never returning null from your own methods. Return an empty list or a default value instead.
5. Why does my code work in IntelliJ but fail at runtime?
This usually means you have a runtime error, not a compile-time one. The compiler is happy with your syntax, but the actual values during execution cause problems. Check your inputs, null states, and array sizes.
6. What does "Exception in thread main" mean?
It means an uncaught exception was thrown inside your main method and stopped the program. Read the rest of the message to find out which type of exception and which line caused it.
7. Should I always use try-catch in my Java code?
Use try-catch around code that touches the outside world, like user input, files, network calls, or parsing. For your own internal logic, fixing the root cause is better than wrapping everything in try-catch.
8. How do I fix StackOverflowError in recursion?
Make sure your recursive method has a base case that actually gets reached. Then check that each recursive call moves toward that base case. If both look right and you still get the error, your recursion is just too deep, and you may need to switch to a loop.
9. Why does Integer.parseInt throw NumberFormatException?
It throws this error when the String you pass in is not a valid number. Common causes are extra spaces, letters mixed in, empty strings, or null. Always trim the String first and wrap the call in a try-catch block.
10. What is ConcurrentModificationException and how do I avoid it?
This error happens when you change a collection while looping through it. To avoid it, use removeIf(), the Iterator's remove() method, a copy of the list, or a CopyOnWriteArrayList.
11. What is the difference between throw and throws in Java?
throw is a statement inside a method that throws one exception right now, like throw new IllegalArgumentException("Age cannot be negative"). throws goes in the method signature and warns callers that the method might throw certain exceptions, like throws IOException. You use throw to raise the problem and throws to pass a checked exception up to the caller.
12. Is NumberFormatException a checked or unchecked exception?
It is unchecked. NumberFormatException extends IllegalArgumentException, which extends RuntimeException, so the compiler does not force you to catch it. That is why your code compiles fine and then crashes when the input is bad. Catch it anywhere you parse text that comes from a user or a file.
13. How do I create a custom exception in Java?
Make a class that extends Exception for a checked exception, or RuntimeException for an unchecked one. Add a constructor that takes a message and passes it to super(message). Then throw it with throw new YourException("...") wherever your rule is broken, like withdrawing more money than an account holds.
14. How do I stop InputMismatchException when using Scanner?
Check the input before you read it. Use hasNextInt() in a loop, and if it returns false, call next() to throw away the bad word and ask again. If you catch the exception instead, you still have to call next() or nextLine(), or the same bad input stays in the Scanner and your loop never ends.
15. Can increasing the JVM stack size fix a StackOverflowError?
Only when your recursion is correct and just very deep. Running your program with java -Xss2m gives each thread more stack space. But if the base case is missing or never reached, the program still crashes, just a little later. Fix the base case first, and switch to a loop if the input can be very large.
Stuck on a Java assignment with errors you cannot fix?
Our tutors have helped thousands of students debug their Java homework and understand the why behind every error. Whether you are battling a stubborn NullPointerException or your recursion just will not stop, we can walk you through it step by step.
Check out our live Java tutoring to get one-on-one support from real coders who teach in plain English.
