498
Control Lab FRC Programming Curriculum
Fundamentals · L13 of 16

Error Handling

Prereqs: fundamentals-12
Objectives 0 / 5

Hook

It’s autonomous. Your robot is lined up perfectly. Three seconds in — the Driver Station goes red. NullPointerException in DriveSubsystem.java, line 47.

The robot sits still for the rest of the match.

A null sensor object. One missing initialization line. The exception crashed the entire robot program.

Exceptions are Java’s way of saying “something went so wrong I can’t continue.” Understanding them — what causes them, how to catch them, and better yet how to prevent them — is the difference between a robot that fails gracefully and one that parks itself mid-field.

Core concept

Key Concept

An exception is a runtime event that interrupts normal program flow when something unexpected happens — a null reference, an out-of-bounds index, an illegal argument. Java lets you catch exceptions to recover or log them, but the better strategy is usually defensive coding: checking for bad conditions before they cause a crash.

Walk-through: What exceptions are

Exceptions are objects

When Java encounters a problem it can’t handle, it creates an exception object and throws it. Execution stops at that line and Java unwinds the call stack looking for a handler. If it doesn’t find one, the program terminates.

Every exception is an object that extends Throwable. The ones you’ll encounter most on an FRC robot:

ExceptionCause
NullPointerExceptionCalling a method on a variable that is null
ArrayIndexOutOfBoundsExceptionAccessing an array at an index that doesn’t exist
IllegalArgumentExceptionPassing a value a method refuses to accept
ClassCastExceptionCasting an object to a type it isn’t
NumberFormatExceptionParsing a string that isn’t a valid number
StackOverflowErrorInfinite recursion (technically an Error, not Exception)

NullPointerException in detail

null means “no object here.” If you call any method on null, Java throws NullPointerException:

String name = null;
int length = name.length();   // NullPointerException — name is null

In FRC this often happens when a sensor object wasn’t initialized:

// RobotContainer.java
private ColorSensor colorSensor;   // declared but never assigned — stays null

// Later:
String color = colorSensor.getColor();  // NullPointerException at runtime

ArrayIndexOutOfBoundsException in detail

Arrays have a fixed size. Valid indices are 0 through length - 1. Anything else throws:

int[] speeds = {10, 20, 30};
System.out.println(speeds[3]);   // ArrayIndexOutOfBoundsException — only indices 0,1,2 exist

In FRC you might see this when reading a fixed-size array from a sensor API and accidentally requesting index 4 on a 4-element array (valid range: 0–3).

IllegalArgumentException in detail

Methods can throw this to reject values they can’t work with:

public void setSpeed(double speed) {
    if (speed < -1.0 || speed > 1.0) {
        throw new IllegalArgumentException("Speed must be between -1 and 1, got: " + speed);
    }
    motor.set(speed);
}

WPILib throws IllegalArgumentException in several places — for example, if you try to register two subsystems with the same name.


Walk-through: try/catch

Basic syntax

try {
    // code that might throw
    int result = Integer.parseInt(userInput);
    System.out.println("Parsed: " + result);
} catch (NumberFormatException e) {
    // runs only if parsing fails
    System.out.println("Bad input: " + userInput + " — defaulting to 0");
}
  • The try block runs normally.
  • If an exception of the specified type is thrown anywhere inside try, execution jumps immediately to the matching catch block.
  • If no exception is thrown, catch is skipped entirely.
  • Execution continues after the catch block either way.

Catching multiple types

try {
    String raw = config.getString("armTarget");
    double target = Double.parseDouble(raw);
    setArmTarget(target);
} catch (NullPointerException e) {
    System.out.println("Config key 'armTarget' not found, using default.");
    setArmTarget(90.0);
} catch (NumberFormatException e) {
    System.out.println("Config value isn't a number: " + e.getMessage());
    setArmTarget(90.0);
}

Each catch clause handles a specific exception type. Java matches the first one that fits and skips the rest.

The finally block

Code in finally runs whether or not an exception was thrown — useful for releasing resources:

FileWriter log = null;
try {
    log = new FileWriter("robot_log.txt");
    log.write("Match started");
} catch (IOException e) {
    System.out.println("Could not write log: " + e.getMessage());
} finally {
    if (log != null) {
        try { log.close(); } catch (IOException ignored) {}
    }
}

In FRC robot code you’ll rarely need finally, but you’ll see it in file-logging and network utilities.

Getting information from an exception

} catch (IllegalArgumentException e) {
    System.out.println("Error: " + e.getMessage());
    e.printStackTrace();   // prints the full call stack to the console — useful for debugging
}

e.getMessage() returns the description string. e.printStackTrace() dumps the full call stack — very useful when debugging, but noisy in production.

Note

In WPILib projects, DriverStation.reportError(message, true) is preferred over e.printStackTrace() because it routes the message to the Driver Station console and the log file simultaneously.


Walk-through: Defensive coding

Catching exceptions is the last resort. The better strategy is to prevent the problem before it can occur.

Null checks

Before calling any method on an object that might be null, check first:

// Risky:
String color = colorSensor.getColor();

// Safe:
if (colorSensor != null) {
    String color = colorSensor.getColor();
} else {
    System.out.println("Color sensor not available, skipping.");
}

Pattern: assign a safe default if the real value is absent.

private double readSensorAngle() {
    if (encoder == null) {
        return 0.0;   // safe default — won't crash the loop
    }
    return encoder.getAngle();
}

Bounds checks

Before indexing an array or list, verify the index is in range:

public double getAutoSpeed(int step) {
    if (step < 0 || step >= autoSpeeds.length) {
        System.out.println("Step " + step + " out of range.");
        return 0.0;
    }
    return autoSpeeds[step];
}

Validating inputs before use

public void setArmDegrees(double degrees) {
    if (degrees < 0 || degrees > 180) {
        System.out.println("Arm target " + degrees + " out of range. Clamping.");
        degrees = Math.max(0, Math.min(180, degrees));
    }
    armPID.setSetpoint(degrees);
}

Safe config reading in FRC

Robots often read configuration from files or Preferences. Those values might be missing or malformed:

import edu.wpi.first.wpilibj.Preferences;

public double readArmTarget() {
    // Preferences.getDouble returns a default value if the key doesn't exist —
    // no try/catch needed because the API is designed defensively.
    return Preferences.getDouble("armTarget", 90.0);
}

When an API already handles the missing-value case, let it. Only reach for try/catch when the API forces you to deal with exceptions directly.


When to catch vs. when to prevent

ScenarioPreferred approach
Sensor not initializedNull check at construction / initialization
Array index from user input or configBounds check before access
Parsing a config string that should be a numbertry/catch NumberFormatException with a logged default
WPILib API that throws if arguments are wrongFix the argument — don’t catch and swallow
Network/file operation that might fail (logging, dashboard)try/catch — failure shouldn’t crash the robot
A bug you haven’t found yetDon’t catch — let it crash so you see the problem
⚠ Heads up

Never write catch (Exception e) {} with an empty body. This silently swallows every exception — including bugs you need to know about. Always log or handle the exception in a meaningful way.


Interactive trace

Code Tracer
01String input = "90";// normal string from config
02parsed = Integer.parseInt(input);// succeeds — 90
03target = parsed;// target set to 90
04── new scenario: input = "ninety" ──// bad config value
05parsed = Integer.parseInt(input);// throws! execution jumps to catch
06catch (NumberFormatException e) { target = 90; }// recover with default — robot keeps running
State
input"90"
parsed0
target0
errornone

Step through to see values update.

Initial state

Key takeaways

  • Exceptions are objects thrown at runtime when something unexpected happens; they halt execution unless caught.
  • The most common exceptions in FRC code are NullPointerException, ArrayIndexOutOfBoundsException, and IllegalArgumentException.
  • try/catch lets you recover from an exception: the catch block runs only when the matching exception type is thrown.
  • Defensive coding — null checks, bounds checks, input validation — is almost always better than catching exceptions.
  • Never swallow exceptions silently. Always log them with a message or a default so you can diagnose problems later.
  • Some API methods (like Preferences.getDouble) are already written defensively; use their default-value parameters instead of try/catch.

Common confusions

“My catch block never runs even though the code throws.”

The exception type in catch must match exactly. If the code throws ArrayIndexOutOfBoundsException but you catch NullPointerException, Java doesn’t match it and the program still crashes. Use catch (Exception e) temporarily to discover what type is actually being thrown (read e.getClass().getName()), then switch to the specific type.

“I wrapped everything in try/catch and now bugs are disappearing.”

That’s the danger of catch-all blocks. If you catch Exception and print nothing, errors vanish silently. You’ll have mysterious behavior with no stack trace. Always print or log inside every catch block.

“Should I catch RuntimeException on every periodic() call to prevent crashes?”

Generally no. Periodic methods run 50 times per second. If something throws every loop, you need to fix the bug, not hide it. The exception to this rule is logging and dashboard code, where a failure should not interrupt robot control.

“What’s the difference between an Exception and an Error?”

Both extend Throwable, but Error signals a serious JVM-level problem (like StackOverflowError or OutOfMemoryError). You should almost never catch an Error. Exception is what application code throws and catches.


Challenge

⚡ Try it yourself

Write a method safeGet(int[] arr, int index) that returns the element at index if the index is valid, or -1 if it is out of bounds. Test it with an array of five values and several index requests, some valid and some out of range.

Expected output for the test cases below:

arr[1] = 20
arr[4] = 50
arr[5] = -1 (out of bounds)
arr[-1] = -1 (out of bounds)
Code EditorJavaCtrl+Enter to run
Stuck? Show hint

Check index >= 0 AND index < arr.length before accessing arr[index]. If either condition fails, return -1 immediately without touching the array.


What’s next

You can now write code that survives unexpected runtime conditions. The next step up in data management is Lesson 14 — Collections: dynamic lists and key-value maps that let you store and look up robot data without being stuck with fixed-size arrays.