Java

Final vs Finally vs Finalize in Java

Final vs Finally vs Finalize in Java: The Ultimate Guide to Avoid Confusion

Java developers often get confused between final, finally, and finalize. These three terms might sound similar, but they serve completely different purposes. If you’ve ever struggled to understand their differences, this guide is for you!

By the end of this post, you’ll have a clear understanding of final vs finally vs finalize in Java and how to use each one correctly. Let’s dive in!

1. What is final in Java?

The final keyword in Java is used for constants, method restrictions, and inheritance control. It can be applied to variables, methods, and classes.

a) final with Variables (Constant Values)

When a variable is declared final, its value cannot be changed once assigned.

Java
public class FinalVariableExample {
    final int MAX_VALUE = 100; // Constant value
    
    void display() {
        // MAX_VALUE = 200; // This will cause a compilation error
        System.out.println("Max Value: " + MAX_VALUE);
    }
}

The MAX_VALUE variable is declared as final, so its value cannot be modified.

b) final with Methods (Prevent Overriding)

A final method cannot be overridden by subclasses.

Java
class Parent {
    final void show() {
        System.out.println("This is a final method.");
    }
}

class Child extends Parent {
    // void show() { // This will cause a compilation error
    //     System.out.println("Cannot override a final method");
    // }
}

The show() method in the Parent class is marked final, preventing the Child class from overriding it.

c) final with Classes (Prevent Inheritance)

A class declared as final cannot be extended.

Java
final class FinalClass {
    void display() {
        System.out.println("This is a final class.");
    }
}

// class SubClass extends FinalClass { // This will cause a compilation error
// }

The FinalClass cannot be extended by any subclass.

2. What is finally in Java?

The finally block in Java is used to ensure that important code executes, regardless of exceptions. It is primarily used with try-catch blocks to handle exceptions.

Java
public class FinallyExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // This will cause an exception
        } catch (ArithmeticException e) {
            System.out.println("Exception caught: " + e.getMessage());
        } finally {
            System.out.println("Finally block executed!");
        }
    }
}

Output:

Java
Exception caught: / by zero
Finally block executed!

The finally block runs no matter what happens in the try-catch block. This is useful for closing resources like database connections or file streams.

3. What is finalize() in Java?

The finalize() method is used for garbage collection. It is called by the Garbage Collector before an object is destroyed to perform cleanup operations.

Java
class FinalizeExample {
    protected void finalize() {
        System.out.println("Finalize method called before garbage collection.");
    }

    public static void main(String[] args) {
        FinalizeExample obj = new FinalizeExample();
        obj = null; // Making object eligible for garbage collection
        System.gc(); // Requesting garbage collection
        System.out.println("End of main method.");
    }
}

Output (may vary depending on JVM execution):

Java
End of main method.
Finalize method called before garbage collection.
  • The finalize() method is called before an object is garbage collected but not guaranteed to execute immediately or at all.
  • Calling System.gc() only suggests garbage collection to the JVM, but it does not force it.
  • Due to unpredictability and performance issues, finalize() has been deprecated in Java 9 and removed (marked as remove) in Java 18.
Alternatives to finalize():
  • Try-with-resources (AutoCloseable) – For handling resources like files, sockets, and streams.
  • java.lang.ref.Cleaner (Java 9+) – A more reliable way to register cleanup actions when objects become unreachable.
Important Note:

The use of finalize() is strongly discouraged in modern Java programming. Developers should use explicit resource management instead of relying on garbage collection for cleanup.

Final vs Finally vs Finalize in Java: Key Differences

Featurefinalfinallyfinalize()
UsageVariable, method, or class modifierBlock in exception handlingMethod in garbage collection
EffectRestricts variable reassignment, method overriding, and class inheritanceEnsures execution of critical codeAllows cleanup before object removal
ExecutionCompile-timeAlways runs after try-catchCalled by garbage collector
PurposeRestrictionCode execution assuranceCleanup

When to Use Final, Finally, and Finalize?

  • Use final when you want to create constants, prevent method overriding, or restrict class inheritance.
  • Use finally when you need to execute important code regardless of exceptions, like closing resources.
  • Use finalize() only if you need to clean up resources before garbage collection, though it is now discouraged.

Conclusion

Understanding final vs finally vs finalize in Java is crucial for writing efficient and error-free Java programs. While final is used for constants, method restrictions, and inheritance prevention, finally ensures essential code execution, and finalize() helps with garbage collection (though deprecated in Java 9+).

custom exceptions

Custom Exceptions vs. Standard Exceptions in Java: When to Extend and When Not To

Java provides a robust exception handling mechanism that helps developers write reliable and maintainable code. While Java’s standard exceptions cover many common error scenarios, sometimes you need something more specific to your application’s needs. This is where custom exceptions in Java come into play. But when should you create a custom exception, and when is it unnecessary? Let’s explore this in depth.

What Are Standard Exceptions in Java?

Java has a rich hierarchy of built-in exceptions that developers can use to handle different errors. These standard exceptions fall into two main categories:

1. Checked Exceptions — Must be handled using try-catch or declared using throws.

  • Example: IOException, SQLException

2. Unchecked Exceptions (Runtime Exceptions) — Do not require explicit handling.

  • Example: NullPointerException, IndexOutOfBoundsException

Using standard exceptions is often the best choice because they are well-documented and understood by developers. However, they might not always convey specific application-related issues effectively.

When to Use Custom Exceptions in Java

Custom exceptions are useful when you need to represent domain-specific errors that are not covered by standard exceptions. Here are some scenarios where custom exceptions make sense:

1. When Standard Exceptions Are Too Generic

Standard exceptions may not always provide enough clarity. For instance, if your application processes payments, throwing a generic Exception or IllegalArgumentException isn’t informative. A PaymentProcessingException makes the error clearer.

2. When You Need to Add Extra Information

A custom exception allows you to include additional details about an error, such as error codes, messages, or even metadata.

3. When You Want to Enforce Business Rules

Custom exceptions help enforce specific business logic. For example, if a user tries to withdraw more money than available, you might throw an InsufficientFundsException instead of a generic RuntimeException.

4. When You Need to Handle Exceptions Differently

If your application has a centralized error-handling mechanism, custom exceptions can be helpful in distinguishing different types of errors.

How to Create a Custom Exception in Java

Creating a custom exception in Java is simple. You can extend either Exception (for checked exceptions) or RuntimeException (for unchecked exceptions).

Creating a Checked Custom Exception

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

public class CustomExceptionExample {
    public static void validateAge(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException("Age must be 18 or above.");
        }
    }
    public static void main(String[] args) {
        try {
            validateAge(16);
        } catch (InvalidAgeException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

Here,

  • InvalidAgeException extends Exception, making it a checked exception.
  • The constructor passes a custom message to the superclass (Exception).
  • The validateAge method throws InvalidAgeException if age is below 18.
  • The exception is caught in main and handled gracefully.

Creating an Unchecked Custom Exception

Java
class DatabaseConnectionException extends RuntimeException {
    public DatabaseConnectionException(String message) {
        super(message);
    }
}

public class UncheckedCustomExceptionExample {
    public static void connectToDatabase(boolean connectionStatus) {
        if (!connectionStatus) {
            throw new DatabaseConnectionException("Failed to connect to the database.");
        }
    }
    public static void main(String[] args) {
        connectToDatabase(false);
    }
}

Here,

  • DatabaseConnectionException extends RuntimeException, making it unchecked.
  • No need to declare it using throws since unchecked exceptions don’t require explicit handling.
  • If connectToDatabase(false) is called, an exception is thrown.

When NOT to Use Custom Exceptions

While custom exceptions in Java are useful, overusing them can lead to unnecessary complexity. Here are cases where they may not be needed:

1. When a Standard Exception Suffices

If a standard exception like IllegalArgumentException or NullPointerException properly conveys the issue, using a custom exception is redundant.

Java
public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative.");
    }
}

There’s no need for a NegativeAgeException when IllegalArgumentException works perfectly.

2. When They Add Unnecessary Complexity

If an exception doesn’t add meaningful information or handling logic, it might not be worth creating.

3. When Logging and Debugging Are Not Improved

If a custom exception doesn’t make debugging easier or doesn’t offer additional insights, it may not be necessary.

Best Practices for Custom Exceptions

  1. Keep Custom Exceptions Specific — Avoid generic names like MyAppException; use names that reflect the issue, such as UserNotFoundException.
  2. Extend the Right Class — Use Exception for checked exceptions and RuntimeException for unchecked exceptions.
  3. Include Helpful Messages — Provide meaningful messages to help with debugging.
  4. Document Your Exceptions — Ensure other developers understand when and why to use them.
  5. Avoid Creating Too Many Exceptions — Use them only when they add real value.

Conclusion

Custom exceptions in Java are powerful when used appropriately. They provide clarity, enforce business logic, and enhance maintainability. However, standard exceptions should be preferred when they adequately describe an error. The key is to strike the right balance — use custom exceptions only when they genuinely improve code readability, debugging, and error handling.

Checked Exceptions

Checked Exceptions in Java: What They Are and How They Work

When writing Java programs, handling errors is an essential part of creating robust and reliable applications. One important concept in Java’s error-handling mechanism is checked exceptions. If you’re new to Java or need a refresher, this guide will walk you through what checked exceptions are, how they work, and how to handle them effectively.

What Are Checked Exceptions in Java?

Checked exceptions in Java are a category of exceptions that must be either caught or declared in the method signature using the throws keyword. They are part of Java’s mechanism to enforce error handling at compile-time, ensuring that developers acknowledge and manage potential problems before running the program.

Unlike unchecked exceptions, which arise due to programming errors (such as NullPointerException or ArrayIndexOutOfBoundsException), checked exceptions typically indicate recoverable conditions like missing files, failed network connections, or invalid user input.

Let’s understand this with a simple example.

Imagine you are booking a flight online. There are two possible situations:

  1. You enter correct details, and the ticket is booked successfully.
  2. You enter an incorrect credit card number, and the system stops the booking process, showing an error.

In Java terms:

  • Booking the flight successfully is like a normal method execution.
  • Entering an invalid card number is like a checked exception, because the system knows this issue could happen and forces you to handle it (e.g., by showing an error message).

How Does Java Enforce Checked Exceptions?

When writing Java code, some operations have a high chance of failing, like:

  • Reading a file (the file may not exist) → IOException
  • Connecting to a database (the database might be down) → SQLException
  • Waiting for a thread to complete → InterruptedException

Since these errors are expected, Java forces you to either:

  • Handle them using try-catch
  • Declare them using throws in the method signature

Let’s say we want to read a file. There’s a chance the file doesn’t exist, so Java forces us to handle this situation.

Without Handling (Compilation Error)

Java
import java.io.FileReader;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        FileReader file = new FileReader("path\data.txt"); // Compilation Error!
    }
}

Error: Unhandled exception: java.io.FileNotFoundException
 Java stops compilation because we didn’t handle the exception. Also the compiler suggests two options: the first one is to surround the code with a try-catch block, and the second is to declare the exception using the throws keyword in the method signature.

Handling with try-catch

We handle the error inside the method using try-catch:

Java
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            FileReader file = new FileReader("path\data.txt"); 
            System.out.println("File opened successfully.");
        } catch (IOException e) {
            System.out.println("Error: File not found.");
        }
    }
}

Output if file exists: File opened successfully.
Output if file is missing: Error: File not found.

Handling with throws (Delegating the Exception)

Instead of handling it inside the method, we can let the caller handle it by declaring throws in the method signature.

Java
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionExample {
    public static void main(String[] args) throws IOException {
        FileReader file = new FileReader("path\data.txt"); 
        System.out.println("File opened successfully.");
    }
}

This approach is useful for propagating exceptions to higher-level methods where they can be handled appropriately.

Why Are Checked Exceptions Important?

Checked exceptions serve an important role in Java by enforcing better error handling. Here’s why they matter:

  1. Compile-Time Safety: They prevent runtime failures by ensuring errors are anticipated and handled during development.
  2. Encourages Robust Code: Developers are forced to think about possible failure scenarios and how to deal with them.
  3. Improves Code Maintainability: Explicit exception declarations make it clear which methods can fail, improving readability and maintainability.

Conclusion

Checked exceptions in Java play a crucial role in enforcing proper error handling at compile-time. By understanding how they work and following best practices, you can write cleaner, more reliable Java code. Whether you use try-catch blocks or declare exceptions with throws, handling checked exceptions properly ensures your applications run smoothly and recover gracefully from potential issues.

By integrating these techniques into your Java development workflow, you’ll be better prepared to handle unexpected situations, making your applications more robust and user-friendly.

Unchecked Exceptions in Java

Unchecked Exceptions in Java: What They Are

Java is a powerful programming language that provides robust error handling mechanisms through exceptions. Exceptions in Java are classified into checked exceptions and unchecked exceptions. In this blog post, we’ll dive deep into unchecked exceptions in java, focusing on RuntimeException, and explore how they work, when to use them, and best practices.

What Are Unchecked Exceptions in Java?

Unchecked exceptions in Java are exceptions that occur during the execution of a program and do not need to be explicitly declared or handled. They are subclasses of RuntimeException, which itself extends Exception. Unlike checked exceptions, the compiler does not force you to handle unchecked exceptions, giving developers more flexibility.

Imagine you are driving a car:

  • If you run out of fuel before starting, you already know you’ll need to refill (like a checked exception, where Java warns you in advance).
  • If you suddenly get a flat tire while driving, it’s unexpected (like an unchecked exception, because Java doesn’t force you to check for it).

Unchecked exceptions usually happen due to coding mistakes like dividing by zero, accessing an invalid index, or dereferencing null.

Key Characteristics of Unchecked Exceptions:

  • They occur at runtime.
  • They are not required to be handled using try-catch or declared with throws
  • They indicate programming errors, such as logical flaws or improper API usage.
  • Examples include NullPointerException, ArrayIndexOutOfBoundsException, and IllegalArgumentException.

Common Causes of Unchecked Exceptions

Unchecked exceptions often arise from:

  1. Null references — Trying to access methods or fields of a null object leads to a NullPointerException.
  2. Invalid array access — Accessing an index beyond the array’s length results in ArrayIndexOutOfBoundsException.
  3. Illegal operations — Dividing by zero throws an ArithmeticException.
  4. Invalid casting — Trying to cast an object to an incompatible type leads to ClassCastException.
  5. Improper argument usage — Passing an invalid argument to a method can trigger IllegalArgumentException.

How to Handle Unchecked Exceptions in Java?

Although unchecked exceptions don’t require explicit handling, it is good practice to write defensive code to avoid them. Here are some best practices:

1. Use Null Checks

Before using an object, always ensure it is not null to avoid NullPointerException.

2. Validate Input Arguments

Check method parameters before processing them.

3. Use Try-Catch Blocks Sparingly

Try-catch blocks should not be overused for unchecked exceptions but can be useful in specific cases.

Difference Between Checked and Unchecked Exceptions

Understanding the distinction between checked and unchecked exceptions is crucial for writing efficient Java code.

FeatureChecked ExceptionsUnchecked Exceptions
InheritanceExtends Exception (except RuntimeException)Extends RuntimeException
Compile-time CheckingChecked by the compilerNot checked by the compiler
Handling RequirementMust be handled or declaredNo mandatory handling
Use CaseRepresent recoverable conditions (e.g., IOException)Indicate programming errors (e.g., NullPointerException)

Should You Catch Unchecked Exceptions in Java?

Generally, it’s best to avoid catching unchecked exceptions unless you’re implementing a global exception handler. Instead, focus on writing clean, error-free code by using input validation and proper null checks. However, in web applications or frameworks, handling unchecked exceptions globally can enhance user experience by providing clear error messages rather than allowing the application to crash.

Conclusion

Unchecked exceptions in Java, particularly those derived from RuntimeException, provide flexibility but also require responsible usage. They indicate programming mistakes that should be fixed rather than caught. By following best practices like validating inputs, using meaningful messages, and logging exceptions properly, developers can write robust and maintainable Java applications.

How to create threads in java

How to Create Threads in Java (The Easy Way): From Thread Class to Runnable Explained

If you’re diving into Java programming, one of the most empowering features to understand is multithreading. It allows you to run multiple tasks concurrently, making your application more efficient and responsive. In this post, we’ll walk you through how to create threads in Java, using both the Thread class and the Runnable interface.

Why Multithreading Matters in Java

Multithreading helps your program perform multiple operations simultaneously. Think of downloading files, processing data, and updating the UI all at once. Without multithreading, these tasks would run one after the other, slowing everything down.

Java makes multithreading easy thanks to built-in support via the Thread class and the Runnable interface.

Method 1: Using the Thread Class

This is the most direct way to create a thread in Java.

Step-by-step:

  1. Extend the Thread class.
  2. Override the run() method.
  3. Create an object of your class.
  4. Call the start() method.
Java
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running...");
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

What Happens Here?

  • run() defines the code that runs in the new thread.
  • start() creates a new thread and invokes run().
  • If you call run() directly, it runs in the main thread, not a new one.

This method is simple but has a limitation: your class can’t extend any other class since Java doesn’t support multiple inheritance.

Method 2: Implementing the Runnable Interface

A more flexible way to create threads in Java is by implementing the Runnable interface.

Step-by-step:

  1. Implement Runnable.
  2. Override the run() method.
  3. Pass the object to a Thread constructor.
  4. Start the thread.
Java
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread is running...");
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.start();
    }
}

Why Use Runnable?

  • It allows your class to extend another class while still supporting threads.
  • It separates the task (logic in run()) from the thread execution (managed by Thread).

Thread Class vs Runnable Interface: Quick Comparison

FeatureThread ClassRunnable Interface
Inheritance LimitationYes (extends Thread)No (implements Runnable)
Separation of ConcernsNoYes
Recommended ForSimple one-off threadsBetter architecture & reuse

Pro Tip: Use Anonymous Classes or Lambdas (Java 8+)

If you’re working with short-lived tasks, you don’t need to write a separate class.

Anonymous Runnable:

Java
Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("Anonymous thread running");
    }
});
t.start();

Lambda Runnable (Java 8+):

Java
Thread t = new Thread(() -> {
    System.out.println("Lambda thread running");
});
t.start();

Best Practices for Creating Threads in Java

  • Avoid calling run() directly. Use start() to ensure a new thread is created.
  • Use Runnable when possible. It offers better design flexibility.
  • Name your threads. This makes debugging easier: Thread t = new Thread(runnable, "WorkerThread");
  • Use thread pools for many threads. For heavy-duty multithreading, look into ExecutorService.

Want more details? Check out the full guide: [Main Article URL]

Conclusion

Learning how to create threads in Java isn’t just about writing concurrent code. It’s about writing efficient, clean, and scalable applications.

Start with Thread if you’re just experimenting. Move to Runnable for better design. Embrace lambdas and anonymous classes for quick jobs.

Multithreading is a key skill in Java. Master it, and you unlock a whole new level of performance for your applications.

FAQ: How to Create Threads in Java

Q: Can I start a thread without creating a class?
A: Yes, use anonymous classes or lambdas.

Q: What happens if I call run() instead of start()?
A: The code runs in the main thread, not in a new thread.

Q: Is Runnable better than Thread?
A: Usually yes. It gives more design flexibility and aligns with best practices.

Multithreading in Java

What Is Multithreading in Java? A Beginner-Friendly Guide with Real Examples

If you’re learning Java, you’ve probably come across the term multithreading. It may sound complicated at first, but it’s one of the most powerful features Java offers for building fast and responsive applications.

This guide will break down multithreading in Java. You’ll learn what it is, why it matters, how it works, and how to use it with real examples.

What Is Multithreading?

Multithreading is a way to run multiple tasks (called threads) at the same time within a single Java program.

Think of a thread as a lightweight process. Instead of running your code line-by-line, you can split it into independent tasks that run concurrently. This can lead to better performance, especially on multi-core processors.

Use Cases:

  • Loading data in the background while the UI stays responsive
  • Performing calculations without freezing the main program
  • Downloading files or accessing a database while doing other tasks

Why Use Multithreading in Java?

Java was designed with multithreading in mind. The java.lang.Thread class and java.util.concurrent package give you robust tools to build concurrent applications.

Benefits of using multithreading in Java:

  • Improved performance on multi-core systems
  • Better user experience in desktop and mobile apps
  • Efficient resource utilization

Understanding the Basics: Threads and the JVM

In Java, every application starts with a main thread — the one that runs your main() method. When you create additional threads, you’re allowing your program to do more than one thing at once.

You can create threads in two common ways:

  1. Extending the Thread class
  2. Implementing the Runnable interface

Let’s look at both approaches.

Method 1: Extending the Thread Class

Java
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running");
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // starts a new thread
    }
}
  • MyThread is a class that extends Thread.
  • The run() method holds the code that the thread will execute.
  • Calling start() creates a new thread and executes the run() method.

Note: Calling run() directly won’t create a new thread. Always use start().

Method 2: Implementing Runnable Interface

Java
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread is running");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}
  • MyRunnable implements Runnable, which has a single method run().
  • We pass an instance of MyRunnable to the Thread constructor.
  • Then, we call start() to launch the new thread.

This method is preferred if your class needs to inherit from another class, since Java doesn’t support multiple inheritance.

Multithreading with Sleep and Multiple Threads

Let’s run two threads to see how they operate concurrently:

Java
class MultiThreadDemo extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
            try {
                Thread.sleep(500); // pauses for 500 milliseconds
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }
    }
    
    public static void main(String[] args) {
        MultiThreadDemo t1 = new MultiThreadDemo();
        MultiThreadDemo t2 = new MultiThreadDemo();
        t1.start();
        t2.start();
    }
}

Here,

  • Two threads (t1 and t2) run the same code.
  • They execute concurrently, each printing numbers 1 to 5.
  • Thread.sleep() simulates a delay to better observe the switching.

You might see interleaved output like:

Java
Thread-1 - 1
Thread-0 - 1
Thread-1 - 2
Thread-0 - 2
Thread-1 - 3
Thread-0 - 3
Thread-1 - 4
Thread-0 - 4
Thread-1 - 5
Thread-0 - 5

Thread Lifecycle

A thread in Java has several states:

  1. New — created but not started
  2. Runnable — ready to run
  3. Running — currently executing
  4. Blocked/Waiting — paused due to IO or sleep
  5. Terminated — finished execution

Understanding the lifecycle helps in debugging and optimizing performance.

Best Practices for Multithreading in Java

  1. Avoid shared data conflicts using synchronization
  2. Use higher-level concurrency APIs like ExecutorService for managing multiple threads
  3. Keep threads short-lived when possible
  4. Handle exceptions inside threads to prevent silent failures
  5. Use meaningful thread names to simplify debugging

Conclusion

Multithreading in Java is a core skill for building efficient, high-performance applications. Once you grasp the basics of threads, Runnable, and Thread.sleep(), you can explore more advanced tools like Callable, Future, and thread pools.

Start small, experiment, and practice writing thread-safe code. Java makes it easier than you might think — and the benefits are well worth it.

finalize() Method

Java finalize() Method Explained: Is It Still Relevant in 2025?

Java developers have used the finalize() method for years to handle cleanup operations before an object is garbage collected. But in 2025, does the finalize() method in Java still hold any relevance? With advancements in garbage collection and alternative resource management techniques, many developers question its necessity.

In this blog post, we’ll explore the finalize() method in Java, understand its purpose, see why it has been deprecated, and examine better alternatives.

What is the Java finalize() Method?

The java finalize() method is a special method in Java that belongs to the Object class. It is called by the garbage collector before an object is destroyed. This method was originally designed to provide a mechanism for resource cleanup, such as closing file streams or releasing memory.

Syntax of finalize()

Java
protected void finalize() throws Throwable {
    // Cleanup code
}

Since every class in Java implicitly extends Object, it can override finalize() to perform custom cleanup operations before an object is garbage collected.

How Does finalize() Work?

The java finalize() method is invoked by the Garbage Collector (GC) before an object is removed from memory. However, it is not guaranteed when (or if) it will be executed, making it unreliable for critical cleanup tasks.

Java
class Demo {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize() method called");
    }
}

public class FinalizeExample {
    public static void main(String[] args) {
        Demo obj = new Demo();
        obj = null; // Make the object eligible for garbage collection
        System.gc(); // Suggest the JVM to run garbage collection
        System.out.println("End of main method");
    }
}

Here,

  1. An instance of Demo is created.
  2. The reference is set to null, making it eligible for garbage collection.
  3. System.gc() is called to request garbage collection (not guaranteed to run immediately).
  4. If the garbage collector runs, finalize() executes before the object is destroyed.

Note: When you run the code, you will see one warning as well: ‘Warning: [removal] finalize() in Object has been deprecated and marked for removal.’

Java
warning: [removal] finalize() in Object has been deprecated and marked for removal  
protected void finalize() throws Throwable {  
^

Why is finalize() Deprecated?

Starting with Java 9, the java finalize() method was deprecated due to several issues:

  1. Unpredictability: The garbage collector decides when to execute finalize(), making it unreliable for resource management.
  2. Performance Overhead: finalize() adds extra processing time, slowing down garbage collection.
  3. Security Risks: Malicious code could exploit finalize() to resurrect objects, leading to potential security vulnerabilities.
  4. Better Alternatives Exist: Modern Java provides try-with-resources and PhantomReferences for safer and more efficient resource cleanup.

Note: In Java 18, finalize() wasn’t completely removed, but it was officially marked for removal. Java Enhancement Proposal (JEP) 421 introduced in Java 18 deprecated finalization, signaling that it will be removed in a future version. So while finalize() still exists in Java 18, developers should avoid using it and switch to better alternatives.

Modern Alternatives to finalize()

1. Using try-with-resources (For Auto-Closeable Resources)

Java introduced try-with-resources in Java 7, which automatically closes resources without needing finalize().

Java
import java.io.FileWriter;
import java.io.IOException;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("path\test.txt")) {
            writer.write("Hello, Java!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Why is it Better?
  • Ensures resources are closed immediately after use.
  • No dependency on garbage collection.
  • Improves code readability and performance.

2. Using PhantomReference (For Advanced Cleanup)

If you need to execute cleanup tasks after an object is garbage collected, PhantomReference is a better alternative.

Java
import java.lang.ref.*;

public class PhantomReferenceExample {
    public static void main(String[] args) {
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        MyObject obj = new MyObject();
        PhantomReference<MyObject> phantomRef = new PhantomReference<>(obj, queue);
        obj = null;
        System.gc();
        System.out.println("Phantom reference added to queue: " + (queue.poll() != null));
    }
}
class MyObject {
    @Override
    protected void finalize() {
        System.out.println("Finalizing MyObject");
    }
}
Why is PhantomReference Better?
  • Provides better control over garbage collection.
  • Avoids unpredictability of finalize().
  • Allows cleanup logic to execute only when necessary.

Is finalize() Still Relevant in 2025?

The short answer is no. With its deprecation and removal in newer Java versions, the java finalize() method is no longer a recommended practice. Java developers should use try-with-resources for automatic resource management and PhantomReference for advanced memory cleanup.

Conclusion

The java finalize() method was once a useful tool, but its unpredictable behavior and performance issues have made it obsolete. Modern alternatives like try-with-resources and PhantomReference offer safer and more efficient ways to handle resource cleanup. As of 2025, Java developers should completely avoid finalize() and adopt best practices that align with modern Java standards.

Do you still have legacy code using finalize()? It’s time to refactor and future-proof your Java applications!

throw and throws

How to Use throw and throws Effectively in Java

When working with Java, handling exceptions properly is crucial to writing robust and maintainable applications. Two essential keywords in Java’s exception-handling mechanism are throw and throws. While they may look similar, they serve different purposes. In this guide, we will explore how to use throw and throws effectively in Java, ensuring clarity and proper exception handling.

Understanding throw and throws in Java

Both throw and throws relate to Java’s exception-handling framework, but they are used differently:

  • throw: Used within a method to explicitly throw an exception.
  • throws: Declares exceptions that a method might throw.

Now, let’s explore both in detail with examples.

Understanding throw in Java

The throw keyword in Java is used to manually create and throw an exception object. This allows the programmer to explicitly indicate exceptional conditions in the program flow.

Java
throw new ArithmeticException("/ by zero");

In this example, an ArithmeticException object is created explicitly with the message “/ by zero” and thrown using the throw keyword.

The main objective of the throw keyword is to hand over the created exception object to the JVM manually.

The following two programs yield exactly the same result:

Without throw keyword:

Java
class Test {
    public static void main(String[] args) {
        System.out.println(10/0);
    }
}


//output

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:3)

In this case, the main method is responsible for causing the ArithmeticException and implicitly hands over the exception object to the JVM.

With throw keyword:

Java
class Test {
    public static void main(String[] args) {
        throw new ArithmeticException("/ by zero");
    }
}


//o/p

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:3)

In this case, the main method explicitly creates an ArithmeticException object using throw keyword and hands it over to the JVM.

Here is one more example.

Java
public class ThrowExample {
    static void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("Age must be 18 or above");
        } else {
            System.out.println("Access granted.");
        }
    }
    
    public static void main(String[] args) {
        checkAge(15);  // This will throw an exception
    }
}

Here,

  • The method checkAge(int age) checks whether the given age is 18 or more.
  • If the condition is not met, throw is used to raise an IllegalArgumentException.
  • This terminates program execution unless the exception is caught and handled. The throw statement stops execution of the current block and transfers control to the nearest exception handler.

Understanding throws in Java

In Java, if there is a possibility of a checked exception being thrown within a method, the method must either handle the exception using a try-catch block or declare that it throws the exception using the throws keyword in its method signature.

Java
import java.io.*;

class Test {
    public static void main(String[] args) {
        PrintWriter pw = new PrintWriter("abc.txt");
        pw.println("Hello");
    }
}

//Compilation Error: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

In this example, the PrintWriter constructor can throw a FileNotFoundException, a checked exception. Since it’s not handled within the main method, it results in a compilation error.

To handle this error, you can either use a try-catch block to handle the exception:

Java
import java.io.*;

class Test {
    public static void main(String[] args) {
        try {
            PrintWriter pw = new PrintWriter("abc.txt");
            pw.println("Hello");
        } catch (FileNotFoundException e) {
            // Handle the exception
            e.printStackTrace();
        }
    }
}

Or you can declare that the method throws the exception using the throws keyword:

Java
import java.io.*;

class Test {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter pw = new PrintWriter("abc.txt");
        pw.println("Hello");
    }
}

Using the throws keyword delegates the responsibility of handling the exception to the caller of the method.

Here is another example.

Java
import java.io.IOException;

public class ThrowsExample {
    static void readFile() throws IOException {
        throw new IOException("File not found");
    }
    
    public static void main(String[] args) {
        try {
            readFile();
        } catch (IOException e) {
            System.out.println("Exception handled: " + e.getMessage());
        }
    }
}

Here,

  • The readFile method declares throws IOException, meaning it might throw an IOException.
  • Instead of handling the exception inside readFile, it delegates handling to the caller (main method).
  • The main method catches the exception using a try-catch block, preventing the program from crashing.

Note: It’s recommended to handle exceptions using try-catch blocks where possible, as using throws may propagate exceptions up the call stack without handling them properly.

Key Differences Between throw and throws

Featurethrowthrows
PurposeUsed to explicitly throw an exceptionDeclares exceptions a method can throw
LocationInside the method bodyIn the method signature
Number of ExceptionsCan only throw one exception at a timeCan declare multiple exceptions separated by commas
PropagationStops execution immediately unless handledInforms the caller to handle the exception

Best Practices for Using throw and throws

1. Use throw for Specific Error Conditions

When you detect a problem in your logic, use throw to raise an appropriate exception. Always provide meaningful error messages to help debugging.

2. Use throws for Method-Level Exception Handling

If a method performs an operation that may result in an exception (e.g., file handling, database access), use throws to declare it and let the caller handle it.

3. Catch and Handle Exceptions Properly

Declaring exceptions using throws does not replace exception handling. Always use try-catch blocks where necessary.

4. Avoid Overusing throws

Overusing throws can make a method difficult to use, as the caller must handle multiple exceptions. Only declare exceptions when handling them inside the method is impractical or when the caller needs to decide how to respond.

Conclusion

Understanding how to use throw and throws effectively in Java is key to writing robust applications. throw is used to generate exceptions manually, while throws is used to declare potential exceptions in a method. By following best practices, you can ensure better error handling and maintainable code.

Try-Catch-Finally in Java

Try-Catch-Finally in Java: How Multiple Catch Blocks Fit into Java’s Exception Handling Flow

Try-Catch-Finally in Java is a crucial part of Java programming. It helps prevent programs from crashing due to unexpected errors. Java provides a structured way to handle exceptions using try, catch, and finally blocks. In this post, we’ll explore how multiple catch blocks work in Java’s exception handling flow and how they improve code reliability.

Understanding Try-Catch-Finally in Java

Java provides a structured way to handle exceptions using try, catch, and finally blocks. Let’s break down each component:

  • try block: This is where you place the code that may throw an exception.
  • catch block: Used to handle specific exceptions that may arise in the try block.
  • finally block: This block always executes, regardless of whether an exception occurs or not. It’s typically used for cleanup tasks like closing resources.
Java
try {
    // Risky code
} catch (Exception e) {
    // Handling code
} finally {
    // Clean-up code
}

Control flow in try-catch

Java
try {
    stmt1;
    stmt2;
    stmt3;
} catch(Exception e) {
    stmt4;
}
stmt5;

Case 1: If no exception occurs: Output: (1, 2, 3, 5, Normal Termination)

Case 2: If an exception is raised at statement 2 and the corresponding catch block matches: Output: (1, 4, 5, Normal Termination)

Case 3: If an exception is raised at statement 2 and the corresponding catch block does not match: Output: (1, Abnormal Termination)

Case 4: If an exception is raised at statement 4 or statement 5, it always leads to abnormal termination.

Notes:

  1. If an exception occurs anywhere within the try block, subsequent statements within the try block will not be executed, even if the exception is handled. Therefore, it’s crucial to include only risky code within the try block, and the try block’s length should be kept as short as possible.
  2. Apart from the try block, exceptions might also occur within catch and finally blocks. If any statement outside of the try block raises an exception, it always results in abnormal termination.

try with multiple catch

Using try with multiple catch blocks is a recommended practice in exception handling as it allows for specific handling tailored to each type of exception encountered.

Worst Practice:

Java
try {
    // Risky code
} catch (Exception e) {
    // Use this single catch block for all exceptions
}

Best Practice:

Java
try {
    // Risky code
} catch (ArithmeticException e) {
    // Perform alternative arithmetic operation
} catch (SQLException e) {
    // Use MySQL database instead of Oracle database
} catch (FileNotFoundException e) {
    // Use local file instead of remote file
} catch (Exception e) {
    // Default exception handling
}

In the best programming practice scenario, each catch block is dedicated to handling a specific type of exception. This approach allows for more precise and targeted handling, improving the robustness and reliability of the code. Additionally, it provides flexibility in dealing with different types of exceptions appropriately.

Some Important Loopholes

1. In a try-with-multiple-catch-blocks scenario, it’s crucial to order the catch blocks properly. Child exceptions should be caught before parent exceptions. Failing to do so results in a compile-time error indicating that the exception has already been caught. For instance:

Incorrect:

Java
try {
    // Risky code
} catch (Exception e) {
    // Parent exception catch block
} catch (ArithmeticException e) {
    // Child exception catch block
}

Correct:

Java
try {
    // Risky code
} catch (ArithmeticException e) {
    // Child exception catch block
} catch (Exception e) {
    // Parent exception catch block
}

2. It’s not allowed to declare two catch blocks for the same type of exception within the same try-catch structure. Attempting to do so will result in a compile-time error.

Incorrect:

Java
try {
    // Risky code
} catch (ArithmeticException e) {
    // Catch block for ArithmeticException
} catch (ArithmeticException e) {
    // Another catch block for ArithmeticException (Duplicate)
}

Correct:

Java
try {
    // Risky code
} catch (ArithmeticException e) {
    // Catch block for ArithmeticException
} catch (Exception e) {
    // Catch block for other exceptions
}

Combinations and Rules for Try-Catch-Finally:

  1. In try-catch-finally, the order is important.
  2. Whenever we write try, it’s compulsory to include either catch or finally; otherwise, we will get a compile-time error (try without catch or finally is invalid).
  3. Whenever we write a catch block, a try block must be present; catch without try is invalid.
  4. Whenever we write a finally block, a try block should be present; finally without try is invalid.
  5. Inside try-catch-finally blocks, we can nest additional try-catch-finally blocks; nesting of try-catch-finally is allowed.
  6. Curly braces ({}) are mandatory for try-catch-finally blocks.

Here are some examples to illustrate these rules:

Valid:

Java
try {
    // code
} catch(Exception e) {
    // exception handling code
} finally {
    // cleanup code
}

Invalid (Compile-time Error):

Java
try {
    // code
}
// CE: try without catch or finally

Invalid (Compile-time Error):

Java
catch(Exception e) {
    // exception handling code
}
// CE: catch without try

Invalid (Compile-time Error):

Java
finally {
    // cleanup code
}
// CE: finally without try

Valid:

Java
try {
    try {
        // code
    } catch(Exception e) {
        // inner catch block
    } finally {
        // inner finally block
    }
} catch(Exception e) {
    // outer catch block
} finally {
    // outer finally block
}

Valid:

Java
try {
    // code
} catch(Exception e) {
    // exception handling code
} finally {
    // cleanup code
}

Conclusion

Using Try-Catch-Finally in Java effectively helps make your code more robust. Multiple catch blocks allow you to handle different types of exceptions separately, improving error management and readability. Always follow best practices when structuring your exception handling to ensure your code remains clean and efficient.

Java Exception Hierarchy

Java Exception Hierarchy Explained: A Complete Guide

Java is a powerful, object-oriented programming language that provides a structured way to handle errors using exceptions. Understanding the Java Exception Hierarchy is crucial for writing robust, error-free code. In this guide, we’ll break down Java’s exception system, explore its hierarchy, and show you how to use it effectively.

Java Exception Hierarchy

In Java’s Exception Hierarchy, the Throwable class serves as the root. This class defines two main child classes:

Exception: Exceptions primarily arise from issues within our program and are typically recoverable.

Example:

Java
try {
    // Read data from the remote file located in London
} catch (FileNotFoundException e) {
    // Use a local file and continue the rest of the program normally
}

Error: Errors, on the other hand, are non-recoverable. For instance, if an OutOfMemoryError occurs, programmers are generally powerless to address it, leading to the abnormal termination of the program. It becomes the responsibility of system administrators or server administrators to tackle issues like increasing heap memory.

Java
Throwable
├── Exception
│   ├── RuntimeException
│   │   ├── ArithmeticException
│   │   ├── NullPointerException
│   │   ├── ClassCastException
│   │   ├── IndexOutOfBoundsException
│   │   │   ├── ArrayIndexOutOfBoundsException
│   │   │   └── StringIndexOutOfBoundsException
│   │   └── IllegalArgumentException
│   │       └── NumberFormatException
│   ├── IOException
│   │   ├── EOFException
│   │   ├── FileNotFoundException
│   │   └── InterruptedIOException
│   └── ServletException
└── Error
    ├── VirtualMachineError
    │   ├── StackOverflowError
    │   └── OutOfMemoryError
    ├── AssertionError
    └── ExceptionInInitializerError

Let’s explore each of these in detail.

Exception: Recoverable Issues

Exceptions are events that disrupt the normal flow of a program but are recoverable. These are further categorized into checked and unchecked exceptions.

Checked Exceptions

Checked exceptions must be handled using a try-catch block or declared in the method signature using throws. The compiler ensures they are properly managed.

Common Checked Exceptions

IOException — Related to input/output operations.

  • EOFException: Thrown when an unexpected end of a file or stream is reached.
  • FileNotFoundException: Occurs when a specified file is missing.
  • InterruptedIOException: Thrown when an I/O operation is interrupted.

ServletException — Occurs when an error happens in a Java Servlet.

Unchecked Exceptions (Runtime Exceptions)

Unchecked exceptions are subclasses of RuntimeException and are not checked at compile time. They occur due to programming logic errors and can usually be avoided with proper coding practices.

Common Unchecked Exceptions

ArithmeticException — Thrown when illegal arithmetic operations occur (e.g., division by zero).

NullPointerException — Occurs when trying to access an object reference that is null.

ClassCastException — Happens when an object is cast to an incompatible type.

IndexOutOfBoundsException — Thrown when trying to access an index beyond valid bounds.

  • ArrayIndexOutOfBoundsException: Raised when an array index is out of range.
  • StringIndexOutOfBoundsException: Raised when a string index is invalid.

IllegalArgumentException — Thrown when an invalid argument is passed to a method.

  • NumberFormatException: A specific subclass that occurs when attempting to convert a non-numeric string into a number.

Error: Unrecoverable Issues

Errors represent serious problems that occur at the system level and are usually beyond the control of the application. They typically indicate problems related to the Java Virtual Machine (JVM) or the system itself.

Common Errors in Java

VirtualMachineError — Errors occurring due to resource exhaustion.

  • StackOverflowError: Happens when the call stack overflows due to deep or infinite recursion.
  • OutOfMemoryError: Raised when the JVM runs out of memory and cannot allocate more objects.

AssertionError — Thrown when an assertion fails in Java (assert statement used for debugging).

ExceptionInInitializerError — Occurs when an exception happens inside a static initializer block or a static variable initialization.

Unlike exceptions, errors are not meant to be caught or handled in most cases. Instead, they indicate fundamental issues that require fixing at a deeper level (e.g., optimizing memory usage).

Key Differences Between Exceptions and Errors

Exception Vs. Error

Conclusion

Understanding the Java Exception Hierarchy is key to writing reliable applications. Java categorizes exceptions into checked and unchecked types, each serving a distinct purpose. By handling exceptions effectively, you can prevent crashes, improve debugging, and ensure your application runs smoothly.

Happy Exception Handling..!

error: Content is protected !!