Common Java Errors and How to Fix Them (With Code Examples)

Common Errors in Java and its fixes

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.

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:

FeatureCompile-Time ErrorRuntime ErrorLogic Error
When it happensBefore the code runsWhile the code is runningAfter the code finishes
Who catches itJava compilerJVMYou, by testing
Common causeBad syntax, typos, missing bracketsBad input, null values, bad mathWrong formula or condition
ExampleMissing ; at end of lineNullPointerExceptionReversing a number with / instead of %
Difficulty to fixEasyMediumHard
Stops the program?Yes, will not compileYes, crashes mid-runNo, 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 null value passed to parseInt()
  • Numbers too big for the data type, like "999999999999" for an int
  • 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.

CheckedUnchecked
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 extendsException (but not RuntimeException)RuntimeException
What it usually meansSomething outside your code failed, like a missing fileA bug or bad input inside your code
ExamplesIOException, FileNotFoundException, InterruptedExceptionNullPointerException, 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

throwthrows
Where it goesInside a method bodyIn the method signature
What it doesThrows one exception right nowWarns callers the method might throw these exceptions
What follows itAn exception object, like throw new IllegalArgumentException("...")Exception class names, like throws IOException
How many at onceOneSeveral, separated by commas
Typical useRejecting bad input in your own codePassing 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.

ActionIntelliJ IDEAEclipseVS Code
Start debuggingShift+F9F11F5
Step over (run this line)F8F6F10
Step into (enter the method)F7F5F11
Step out (finish this method)Shift+F8F7Shift+F11
Resume to the next breakpointF9F8F5

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.