How do I create a custom exception in Java?

You can create a custom exception to describe a problem specific to your application. Give it a meaningful name so calling code can recognize and handle the problem.

In this example, you will create an InvalidAgeException and throw it when someone under 18 tries to register.

Source code

Save the following code as CustomExceptionExample.java:

public class CustomExceptionExample {
    void main() {
        try {
            register(16);
        } catch (InvalidAgeException exception) {
            IO.println("Registration failed: " + exception.getMessage());
        }

        IO.println("The program continues.");
    }

    void register(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException(
                    "You must be at least 18 years old.");
        }

        IO.println("Registration successful.");
    }
}

class InvalidAgeException extends Exception {
    InvalidAgeException(String message) {
        super(message);
    }
}

Explanation

The custom exception extends Exception:

class InvalidAgeException extends Exception {
    InvalidAgeException(String message) {
        super(message);
    }
}

This makes InvalidAgeException a checked exception. Code that calls a method declaring this exception must either catch it or declare that it can propagate it.

The constructor accepts a message and passes it to the superclass constructor:

super(message);

Calling code can retrieve that message using getMessage().

Declaring the exception

The register() method declares the exception using throws:

void register(int age) throws InvalidAgeException {

This declaration tells callers that the method may throw an InvalidAgeException.

Throwing the exception

The method checks whether the age meets the registration requirement:

if (age < 18) {
    throw new InvalidAgeException(
            "You must be at least 18 years old.");
}

The throw statement creates and throws the exception. Execution leaves the method immediately, so the success message is skipped.

Notice the difference between the two keywords:

Keyword Purpose
throw Throws a specific exception object
throws Declares exceptions that a method may propagate

Handling the exception

The calling code catches the custom exception:

try {
    register(16);
} catch (InvalidAgeException exception) {
    IO.println("Registration failed: " + exception.getMessage());
}

Since 16 is below the minimum age, the catch block prints the failure message.

Once the exception is handled, the program continues after the try-catch statement.

Run the example

Compile and run the file:

javac CustomExceptionExample.java
java CustomExceptionExample

Output

Registration failed: You must be at least 18 years old.
The program continues.

If you change the method call to register(20), the output becomes:

Registration successful.
The program continues.

Choosing an exception type

Extend Exception when you want callers to catch or declare the exception. Extend RuntimeException when you want an unchecked exception, which callers are not required to catch or declare.

Use a custom exception when its name gives callers useful information or lets them handle a specific failure separately. For a simple invalid argument, a standard exception such as IllegalArgumentException may be sufficient.

How do I implement the Factory Method pattern in Java?

The Factory Method pattern is a Gang of Four (GoF) creational design pattern. It defines an operation for creating a product while allowing subclasses to choose the concrete product returned. This lets a base class reuse a workflow without hard-coding the product implementation into that workflow.

This article continues our creational patterns series after Singleton. Here, we focus on choosing an implementation through subclassing.

The problem

Suppose an application sends notifications by email and SMS. Both channels support the same operation, send(), but each has its own implementation.

We want a notification service to follow a shared workflow: create a notification, then send the message. The shared workflow should work with either channel. Factory Method gives subclasses a creation step to customize.

A complete example

Save the following code as FactoryMethodExample.java. The classes are nested so that the entire example fits into one source file. In a larger application, you can place them in separate files.

package org.kodejava.pattern.factorymethod;

public class FactoryMethodExample {
    interface Notification {
        void send(String message);
    }

    static class EmailNotification implements Notification {
        @Override
        public void send(String message) {
            System.out.println("Email: " + message);
        }
    }

    static class SmsNotification implements Notification {
        @Override
        public void send(String message) {
            System.out.println("SMS: " + message);
        }
    }

    abstract static class NotificationService {
        protected abstract Notification createNotification();

        public void notifyUser(String message) {
            Notification notification = createNotification();
            notification.send(message);
        }
    }

    static class EmailNotificationService extends NotificationService {
        @Override
        protected Notification createNotification() {
            return new EmailNotification();
        }
    }

    static class SmsNotificationService extends NotificationService {
        @Override
        protected Notification createNotification() {
            return new SmsNotification();
        }
    }

    public static void main(String[] args) {
        NotificationService email = new EmailNotificationService();
        email.notifyUser("Your order has been shipped.");

        NotificationService sms = new SmsNotificationService();
        sms.notifyUser("Your verification code is 482731.");
    }
}

The channel implementations print messages to the console to keep the example self-contained. They do not contact an email provider or SMS gateway.

Compile and run from the directory containing the source file:

javac -d out FactoryMethodExample.java
java -cp out org.kodejava.pattern.factorymethod.FactoryMethodExample

Output:

Email: Your order has been shipped.
SMS: Your verification code is 482731.

How it works

The example has four roles:

GoF role Example Responsibility
Product Notification Declares the operation shared by all notifications.
Concrete products EmailNotification, SmsNotification Implement the channel behavior.
Creator NotificationService Declares the factory method and uses its result in a shared workflow.
Concrete creators EmailNotificationService, SmsNotificationService Override the factory method to choose a concrete product.

The factory method is createNotification(). The inherited notifyUser() method calls it and uses the returned Notification without needing to know its concrete class.

Java supports this structure through abstract methods and method overriding: each concrete service supplies the creation behavior declared by its abstract superclass. See Java’s documentation on abstract methods and classes.

The client still selects a concrete service in main(). The pattern moves product creation behind an overridable method; it does not eliminate the need to choose which service the application uses.

Adding another channel

To support push notifications, implement Notification in a new PushNotification class. Then extend NotificationService and override createNotification() to return that product. The existing notifyUser() workflow can remain unchanged.

This example creates a new product for each call. Factory Method itself does not require a new instance every time; an implementation can return a reusable product if its lifecycle and thread-safety requirements allow it.

Factory Method versus a simple factory

A utility such as NotificationFactory.create("email"), with a conditional selecting a concrete class, is commonly called a simple factory. That can be useful, but it does not demonstrate the subclass-based creation mechanism shown here.

In this example, the defining relationship is that the creator’s shared workflow invokes a factory method overridden by its subclasses. Merely naming a method create() does not establish that structure.

When should I use it?

Use Factory Method when a base class has useful shared behavior, but subclasses need to customize the products that behavior uses. It is especially suitable for extension points in frameworks or reusable libraries.

The tradeoff is additional classes and inheritance. If all you need is a small conditional that constructs an object, a simple factory may be sufficient. If callers can supply an already-created Notification, constructor injection may provide a simpler way to customize the behavior.

Next in this series: Abstract Factory, which focuses on creating families of related products.

How do I handle an exception using try-catch in Java?

You can use a try-catch statement to handle an exception and provide a useful response when an operation fails.

In this example, you will try to convert text into an integer. If the text is not a valid integer, the program will display a message and continue running.

Source code

Save the following code as TryCatchExample.java:

public class TryCatchExample {
    void main() {
        String input = "twenty";

        try {
            int number = Integer.parseInt(input);
            IO.println("Parsed number: " + number);
        } catch (NumberFormatException exception) {
            IO.println("Cannot convert \"" + input + "\" to an integer.");
            IO.println("Please enter a whole number, such as 20.");
        }

        IO.println("The program continues.");
    }
}

Explanation

The variable input contains the text to convert:

String input = "twenty";

The try block contains the operation that may throw an exception:

try {
    int number = Integer.parseInt(input);
    IO.println("Parsed number: " + number);
}

Integer.parseInt() converts a valid integer string, such as "20", into an int.

If the text cannot be parsed as an int, the method throws a NumberFormatException. This also happens when the number is outside the range supported by int.

Catching the exception

The catch block handles NumberFormatException:

catch (NumberFormatException exception) {
    IO.println("Cannot convert \"" + input + "\" to an integer.");
    IO.println("Please enter a whole number, such as 20.");
}

Since "twenty" is not a valid integer string, execution moves from Integer.parseInt() to this block.

The remaining statement inside the try block is skipped, so "Parsed number: ..." is not printed.

The parameter exception refers to the exception object. This example provides its own message rather than displaying the exception’s technical details.

Continuing after the catch block

After the exception is handled, execution continues with the statement following the try-catch:

IO.println("The program continues.");

Catching an exception does not retry the failed operation automatically.

Run the example

Compile and run the file:

javac TryCatchExample.java
java TryCatchExample

Output

Cannot convert "twenty" to an integer.
Please enter a whole number, such as 20.
The program continues.

If you change input to "20":

String input = "20";

The conversion succeeds, the catch block is skipped, and the output becomes:

Parsed number: 20
The program continues.

Catch the specific exception you can handle. Here, NumberFormatException clearly identifies the expected conversion failure.

How do I create a record to store data in Java?

A record is a concise way to define a class that holds data. Java automatically provides a constructor, accessor methods, and implementations of equals(), hashCode(), and toString().

In this example, you will create a Book record to store a title and an author.

Source code

Save the following code as RecordExample.java:

public class RecordExample {
    void main() {
        Book firstBook = new Book("Learning Java", "Alice");
        Book secondBook = new Book("Learning Java", "Alice");

        IO.println("Title: " + firstBook.title());
        IO.println("Author: " + firstBook.author());
        IO.println("Record: " + firstBook);
        IO.println("Equal values: " + firstBook.equals(secondBook));
        IO.println("Same object: " + (firstBook == secondBook));
    }
}

record Book(String title, String author) {
}

Explanation

The following declaration defines a record with two components:

record Book(String title, String author) {
}

The components are title and author, both of type String.

For this declaration, Java automatically provides:

Member Purpose
Book(String title, String author) Initializes the components
title() Returns the title
author() Returns the author
equals(Object other) Compares component values
hashCode() Calculates a hash code from the components
toString() Returns a text representation of the record

Creating a record object

Use the new keyword and pass a value for each component:

Book firstBook = new Book("Learning Java", "Alice");

The arguments follow the order of the components in the record declaration.

Reading component values

Record accessors use the component names directly:

firstBook.title()
firstBook.author()

The generated methods are named title() and author(), rather than getTitle() and getAuthor().

Each component has a private final field. Records do not generate setters, so you cannot reassign these fields after construction.

Comparing records

The example creates two separate objects with the same component values:

Book firstBook = new Book("Learning Java", "Alice");
Book secondBook = new Book("Learning Java", "Alice");

The generated equals() method returns true because both objects are instances of the same record class and have equal component values.

The == operator returns false because the variables refer to separate objects.

Printing a record

Passing the record to IO.println() uses its generated toString() method:

IO.println("Record: " + firstBook);

The result includes the record name, component names, and their values.

Run the example

Compile and run the file:

javac RecordExample.java
java RecordExample

Output

Title: Learning Java
Author: Alice
Record: Book[title=Learning Java, author=Alice]
Equal values: true
Same object: false

Are records immutable?

Records are shallowly immutable: their component fields cannot be reassigned, but referenced objects may still be mutable.

This Book record contains only strings, which are immutable. If a record contains a mutable list or array, additional steps are needed to protect that data from changes.

How do I define and use an enum in Java?

An enum defines a fixed set of named constants. You can use it to represent values such as order statuses, directions, or days of the week.

In this example, you will define an enum for order statuses and use a switch expression to select a message.

Source code

Save the following code as EnumExample.java:

public class EnumExample {
    void main() {
        OrderStatus status = OrderStatus.SHIPPED;

        String message = switch (status) {
            case PENDING -> "Your order is waiting for confirmation.";
            case PROCESSING -> "Your order is being prepared.";
            case SHIPPED -> "Your order is on its way.";
            case DELIVERED -> "Your order has arrived.";
        };

        IO.println("Current status: " + status);
        IO.println("Message: " + message);

        IO.println();
        IO.println("Available statuses:");

        for (OrderStatus availableStatus : OrderStatus.values()) {
            IO.println(availableStatus);
        }
    }
}

enum OrderStatus {
    PENDING,
    PROCESSING,
    SHIPPED,
    DELIVERED
}

Explanation

The enum keyword declares a type with a fixed set of constants:

enum OrderStatus {
    PENDING,
    PROCESSING,
    SHIPPED,
    DELIVERED
}

Enum constant names conventionally use uppercase letters, with underscores separating words.

Assigning an enum value

Use the enum type followed by a constant name:

OrderStatus status = OrderStatus.SHIPPED;

The variable status can hold a reference to an OrderStatus constant. You cannot assign a string such as "SHIPPED" directly to it.

This helps prevent mistakes caused by spelling or capitalization differences in string values.

Using an enum in a switch expression

The switch expression selects a message for the current status:

String message = switch (status) {
    case PENDING -> "Your order is waiting for confirmation.";
    case PROCESSING -> "Your order is being prepared.";
    case SHIPPED -> "Your order is on its way.";
    case DELIVERED -> "Your order has arrived.";
};

Inside these case labels, you can use the constant names without the OrderStatus. prefix.

The expression covers every constant declared in OrderStatus, so a default rule is unnecessary.

Looping through enum constants

Every enum provides a values() method that returns an array of its constants in declaration order:

for (OrderStatus availableStatus : OrderStatus.values()) {
    IO.println(availableStatus);
}

Here, the loop prints all four statuses, starting with PENDING.

Run the example

Compile and run the file:

javac EnumExample.java
java EnumExample

Output

Current status: SHIPPED
Message: Your order is on its way.

Available statuses:
PENDING
PROCESSING
SHIPPED
DELIVERED

Use an enum when a value should come from a known set of choices. It gives those choices meaningful names and lets the compiler check their usage.