The throw keyword in Java is used to explicitly throw an exception from a method or any block of code. It's a powerful tool for handling error conditions in your applications.
1. Basic Syntax
throw new ExceptionType("Error message");
2. Basic Throw Examples
Throwing Built-in Exceptions
public class BasicThrowExamples {
// Throwing IllegalArgumentException
public static void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150: " + age);
}
System.out.println("Age set to: " + age);
}
// Throwing NullPointerException
public static void printLength(String text) {
if (text == null) {
throw new NullPointerException("Text cannot be null");
}
System.out.println("Text length: " + text.length());
}
// Throwing ArithmeticException
public static double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("Division by zero is not allowed");
}
return a / b;
}
public static void main(String[] args) {
try {
setAge(25); // Valid
setAge(-5); // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
System.out.println("Caught: " + e.getMessage());
}
try {
printLength("Hello"); // Valid
printLength(null); // Throws NullPointerException
} catch (NullPointerException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}
3. Throwing Checked vs Unchecked Exceptions
Unchecked Exceptions (RuntimeException subclasses)
public class UncheckedExceptionExample {
// No need to declare in method signature
public static void validateEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email format: " + email);
}
System.out.println("Email is valid: " + email);
}
public static void processArray(int[] array, int index) {
if (array == null) {
throw new NullPointerException("Array cannot be null");
}
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException(
"Index " + index + " out of bounds for array length " + array.length);
}
System.out.println("Array element: " + array[index]);
}
}
Checked Exceptions (Must be handled or declared)
import java.io.*;
public class CheckedExceptionExample {
// Must declare checked exceptions in method signature
public static void readConfigFile(String filename) throws IOException {
if (filename == null || filename.trim().isEmpty()) {
throw new IOException("Filename cannot be null or empty");
}
File file = new File(filename);
if (!file.exists()) {
throw new FileNotFoundException("Config file not found: " + filename);
}
// Simulate file reading
System.out.println("Reading config file: " + filename);
}
// Alternative: Handle the exception internally
public static boolean safeReadConfig(String filename) {
try {
if (filename == null) {
throw new IOException("Filename cannot be null");
}
System.out.println("Reading: " + filename);
return true;
} catch (IOException e) {
System.out.println("Error reading config: " + e.getMessage());
return false;
}
}
}
4. Custom Exceptions with Throw
Creating Custom Exceptions
// Custom checked exception
class InsufficientBalanceException extends Exception {
private double currentBalance;
private double requiredAmount;
public InsufficientBalanceException(double currentBalance, double requiredAmount) {
super(String.format("Insufficient balance. Current: $%.2f, Required: $%.2f",
currentBalance, requiredAmount));
this.currentBalance = currentBalance;
this.requiredAmount = requiredAmount;
}
public double getCurrentBalance() { return currentBalance; }
public double getRequiredAmount() { return requiredAmount; }
}
// Custom unchecked exception
class InvalidTransactionException extends RuntimeException {
private String transactionId;
public InvalidTransactionException(String transactionId, String message) {
super("Transaction " + transactionId + ": " + message);
this.transactionId = transactionId;
}
public String getTransactionId() { return transactionId; }
}
Using Custom Exceptions
public class BankingService {
private double balance;
public BankingService(double initialBalance) {
if (initialBalance < 0) {
throw new InvalidTransactionException("INIT",
"Initial balance cannot be negative: " + initialBalance);
}
this.balance = initialBalance;
}
// Using custom checked exception
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount <= 0) {
throw new InvalidTransactionException("WITHDRAW",
"Withdrawal amount must be positive: " + amount);
}
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
System.out.printf("Withdrawn: $%.2f, New balance: $%.2f%n", amount, balance);
}
// Using custom unchecked exception
public void transfer(BankingService target, double amount) {
if (target == null) {
throw new InvalidTransactionException("TRANSFER", "Target account cannot be null");
}
if (amount <= 0) {
throw new InvalidTransactionException("TRANSFER",
"Transfer amount must be positive: " + amount);
}
try {
this.withdraw(amount); // Might throw checked exception
target.deposit(amount);
System.out.printf("Transferred: $%.2f to target account%n", amount);
} catch (InsufficientBalanceException e) {
// Convert checked exception to unchecked for this method
throw new InvalidTransactionException("TRANSFER",
"Insufficient funds for transfer: " + e.getMessage());
}
}
public void deposit(double amount) {
if (amount <= 0) {
throw new InvalidTransactionException("DEPOSIT",
"Deposit amount must be positive: " + amount);
}
balance += amount;
System.out.printf("Deposited: $%.2f, New balance: $%.2f%n", amount, balance);
}
public double getBalance() {
return balance;
}
}
5. Throw in Real-World Scenarios
Validation Framework
public class UserValidator {
public static void validateUser(String username, String email, int age) {
// Collect all validation errors
StringBuilder errors = new StringBuilder();
if (username == null || username.trim().length() < 3) {
errors.append("Username must be at least 3 characters. ");
}
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
errors.append("Invalid email format. ");
}
if (age < 13 || age > 120) {
errors.append("Age must be between 13 and 120. ");
}
// Throw exception if any validation failed
if (errors.length() > 0) {
throw new IllegalArgumentException("Validation failed: " + errors.toString().trim());
}
System.out.println("User validation successful!");
}
public static void validatePassword(String password) {
if (password == null || password.length() < 8) {
throw new SecurityException("Password must be at least 8 characters long");
}
if (!password.matches(".*[A-Z].*")) {
throw new SecurityException("Password must contain at least one uppercase letter");
}
if (!password.matches(".*[a-z].*")) {
throw new SecurityException("Password must contain at least one lowercase letter");
}
if (!password.matches(".*\\d.*")) {
throw new SecurityException("Password must contain at least one digit");
}
}
}
Data Processing with Throw
import java.util.*;
public class DataProcessor {
public static void processData(List<Integer> data, int divisor) {
if (data == null) {
throw new NullPointerException("Data list cannot be null");
}
if (data.isEmpty()) {
throw new IllegalArgumentException("Data list cannot be empty");
}
if (divisor == 0) {
throw new ArithmeticException("Divisor cannot be zero");
}
List<Double> results = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
Integer value = data.get(i);
if (value == null) {
throw new IllegalArgumentException(
"Null value found at index " + i + " in data list");
}
if (value < 0) {
throw new IllegalArgumentException(
"Negative value " + value + " found at index " + i);
}
double result = (double) value / divisor;
results.add(result);
}
System.out.println("Processed results: " + results);
}
// Batch processing with detailed error reporting
public static void processBatch(List<List<Integer>> batches) {
int successful = 0;
int failed = 0;
for (int i = 0; i < batches.size(); i++) {
try {
processData(batches.get(i), 2);
successful++;
} catch (RuntimeException e) {
failed++;
System.err.printf("Batch %d failed: %s%n", i, e.getMessage());
// Log detailed information but don't stop execution
if (e instanceof IllegalArgumentException) {
System.err.println(" -> Input validation error");
} else if (e instanceof ArithmeticException) {
System.err.println(" -> Mathematical operation error");
}
}
}
System.out.printf("Batch processing complete. Successful: %d, Failed: %d%n",
successful, failed);
}
}
6. Advanced Throw Patterns
Exception Chaining
public class ExceptionChaining {
public static void processOrder(String orderId) {
try {
validateOrder(orderId);
processPayment(orderId);
shipOrder(orderId);
} catch (Exception e) {
// Chain the exception with additional context
throw new RuntimeException("Failed to process order: " + orderId, e);
}
}
private static void validateOrder(String orderId) {
if (orderId == null || orderId.isEmpty()) {
throw new IllegalArgumentException("Invalid order ID");
}
System.out.println("Order validated: " + orderId);
}
private static void processPayment(String orderId) {
// Simulate payment failure
if (orderId.startsWith("FAIL")) {
throw new RuntimeException("Payment processing failed for: " + orderId);
}
System.out.println("Payment processed: " + orderId);
}
private static void shipOrder(String orderId) {
// Simulate shipping failure
if (orderId.startsWith("SHIP_FAIL")) {
throw new RuntimeException("Shipping failed for: " + orderId);
}
System.out.println("Order shipped: " + orderId);
}
public static void main(String[] args) {
try {
processOrder("ORDER123");
processOrder("FAIL456"); // This will cause exception chaining
} catch (RuntimeException e) {
System.out.println("Main caught: " + e.getMessage());
System.out.println("Root cause: " + e.getCause().getMessage());
e.printStackTrace();
}
}
}
Conditional Throw with Helper Methods
public class ValidationUtils {
// Helper methods for common validation patterns
public static void requireNonNull(Object obj, String message) {
if (obj == null) {
throw new NullPointerException(message);
}
}
public static void requireNonEmpty(String str, String message) {
if (str == null || str.trim().isEmpty()) {
throw new IllegalArgumentException(message);
}
}
public static void requirePositive(int number, String message) {
if (number <= 0) {
throw new IllegalArgumentException(message);
}
}
public static void requireInRange(int number, int min, int max, String message) {
if (number < min || number > max) {
throw new IllegalArgumentException(
String.format("%s. Value %d must be between %d and %d",
message, number, min, max));
}
}
}
// Using the validation helpers
public class ProductService {
public void addProduct(String name, double price, int quantity) {
ValidationUtils.requireNonEmpty(name, "Product name cannot be empty");
ValidationUtils.requirePositive((int) (price * 100), "Price must be positive");
ValidationUtils.requirePositive(quantity, "Quantity must be positive");
ValidationUtils.requireInRange(quantity, 1, 1000, "Invalid quantity");
System.out.printf("Product added: %s, Price: $%.2f, Quantity: %d%n",
name, price, quantity);
}
public void updateProduct(String productId, String newName) {
ValidationUtils.requireNonEmpty(productId, "Product ID cannot be empty");
ValidationUtils.requireNonEmpty(newName, "Product name cannot be empty");
if (productId.length() != 8) {
throw new IllegalArgumentException("Product ID must be exactly 8 characters");
}
System.out.printf("Product %s updated to: %s%n", productId, newName);
}
}
7. Testing Throw Statements
JUnit Tests for Exception Throwing
import org.junit.Test;
import static org.junit.Assert.*;
public class ThrowTestExamples {
@Test(expected = IllegalArgumentException.class)
public void testSetAgeWithNegativeValue() {
BasicThrowExamples.setAge(-5);
}
@Test
public void testWithdrawWithInsufficientBalance() {
BankingService account = new BankingService(100);
try {
account.withdraw(200); // This should throw exception
fail("Expected InsufficientBalanceException was not thrown");
} catch (InsufficientBalanceException e) {
assertEquals(100.0, e.getCurrentBalance(), 0.001);
assertEquals(200.0, e.getRequiredAmount(), 0.001);
assertTrue(e.getMessage().contains("Insufficient balance"));
}
}
@Test
public void testUserValidatorWithInvalidData() {
try {
UserValidator.validateUser("ab", "invalid-email", 10);
fail("Expected IllegalArgumentException was not thrown");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Validation failed"));
assertTrue(e.getMessage().contains("Username"));
assertTrue(e.getMessage().contains("email"));
assertTrue(e.getMessage().contains("Age"));
}
}
@Test
public void testProcessDataWithNullList() {
try {
DataProcessor.processData(null, 5);
fail("Expected NullPointerException was not thrown");
} catch (NullPointerException e) {
assertEquals("Data list cannot be null", e.getMessage());
}
}
}
8. Best Practices
1. Provide Meaningful Messages
// Good
throw new IllegalArgumentException("Age must be between 0 and 150, but was: " + age);
// Avoid
throw new IllegalArgumentException("Invalid age");
2. Use Appropriate Exception Types
// Use specific exceptions when possible
throw new FileNotFoundException("Config file missing: " + filename);
// Rather than generic
throw new IOException("Config file missing: " + filename);
3. Don't Throw Generic Exceptions Unnecessarily
// Avoid - too generic
public void process() throws Exception {
// code
}
// Prefer - specific exceptions
public void process() throws IOException, SQLException {
// code
}
4. Consider Performance
// Exception creation is expensive, so avoid in performance-critical code
public void validateInput(String input) {
if (input == null) {
// Only create exception when actually needed
throw new NullPointerException("Input cannot be null");
}
}
Summary
throwis used to explicitly throw exceptions- Can throw both built-in and custom exceptions
- Checked exceptions must be declared or handled
- Unchecked exceptions don't require declaration
- Use exception chaining to preserve original exceptions
- Always provide meaningful error messages
- Choose the appropriate exception type for each scenario
The throw keyword is essential for creating robust, self-documenting code that properly handles error conditions and provides clear feedback when things go wrong.