Java Interview Questions & Tips

By The Mighty Anomaly | Published: July 3, 2023

Java Interview Stats

We've hosted over 100k interviews on our platform. Java was the language of choice in those interviews 29% of the time. Java also had the highest success rates – engineers who chose Java as their interview language passed their interviews 51% of the time.

Below is a distribution of programming languages and their popularity in technical interviews as well as success rates in interviews, by language.

Java was the language of choice in interviewing.io interviews 29% of the time

Java had a fairly low interview success rate – engineers who chose Java as their interview language on interviewing.io passed interviews just 51% of the time

Java Idioms & Idiosyncrasies

Java is a popular choice in software development for its portability, simplicity, and its robust, object-oriented capabilities. It's a high-level language that has a great emphasis on readability and simplicity.

As a seasoned and commonly used language, Java boasts a vast feature set and numerous libraries. It’s a language that enforces strict typing and has an extensive standard library. It’s a language and culture with a significant amount of formalism, especially regarding object-oriented programming and type systems.

Most candidates and interviewers will be familiar with Java’s class structure, inheritance rules, and other internals. Focus on the core parts of what make Java great and express your solutions in with good Object-Oriented styles.

  • Classes and Objects: Java is a fully object-oriented language. Ensure you're comfortable with classes, objects, inheritance, polymorphism, and encapsulation.
  • Exception Handling: Java has a robust exception handling mechanism with try, catch, finally, and throw blocks. Understand the difference between checked and unchecked exceptions.
  • Java Collections Framework: Java provides a rich set of prepackaged data structures and algorithms called the Java Collections Framework. Be comfortable with common collections like List, Set, Map, Queue, etc.
  • Generics: Generics provide compile-time type safety that allows programmers to catch invalid types at compile time.
  • Concurrency: Understand the basics of multi-threading, synchronization, and the Java concurrency utilities from the java.util.concurrent package.
  • Stream API: Java 8 introduced the Stream API for functional-style operations on streams of elements. It is beneficial to understand how to use streams for operations like filtering, mapping, or collecting elements to a result container.
  • Java Memory Model: Understand how memory allocation works in Java, including the role of the garbage collector.

Common Java Interview Mistakes

When interviewing in Java, there are a few common pitfalls to avoid.

Ignoring Exceptions

Java's exception handling capabilities are one of its strong points, offering developers a structured way to predict, catch, and handle errors during the execution of the program. These mechanisms consist of try, catch, and finally blocks, as well as throws clauses. Yet, despite the robustness of these features, mishandling exceptions can lead to code that is not only faulty but also difficult to debug and maintain.

One common mistake is ignoring or "swallowing" exceptions. This refers to the practice of catching an exception but not handling it appropriately, typically leaving the catch block empty. This can be problematic as it hides error symptoms that could be critical for diagnosing issues. Swallowing exceptions is especially perilous when it's done at the system's higher levels, which could result in an entire chain of operations failing silently.

For instance, consider the following piece of code:

try {
    int result = 10 / 0; // This line will throw an ArithmeticException
} catch (ArithmeticException e) {
    // This is an example of swallowing an exception
}

In the above example, an ArithmeticException is thrown due to division by zero, but the exception is swallowed in the catch block, leading to a silent failure. This kind of error can be incredibly difficult to track down in a larger codebase because it leaves no trace.

A better practice would be to at least log the exception, giving you a chance to diagnose the problem if something goes wrong. Here's how you might handle the exception more effectively:

try {
    int result = 10 / 0; // This line will throw an ArithmeticException
} catch (ArithmeticException e) {
    System.err.println("An arithmetic error occurred: " + e.getMessage());
}

By appropriately logging the exception, you will get a message in your error log indicating what went wrong, making it easier to debug and fix the issue. In real-world applications, you'd likely use a logging framework like Log4j or SLF4Jinstead of System.err.println, but the principle remains the same.

Remember, ignoring exceptions may result in unpredictable behavior, while proper exception handling leads to more robust, reliable, and maintainable software.

Neglecting Null Checks

Java's object-oriented paradigm permits the use of null references, i.e., references that do not point to any object. While null references can be handy, they also pose a common pitfall: a NullPointerException (NPE). This exception is thrown when a program attempts to use an object reference that has the null value. These include calling an instance method on the object referred to by the null reference or trying to access a field of the object. Forgetting to check for null before accessing object methods or properties is a common oversight that can lead to such crashes.

Take, for example, the following code:

String myString = null;
int length = myString.length(); // This will throw a NullPointerException

In this scenario, myString is null, and calling length() on it results in a NullPointerException. This mistake can be easy to overlook in complex codebases or when dealing with returned values from methods, or objects that have many fields in them, leading to program failure.

A good defensive programming practice is to make null checks before accessing methods or properties of an object. Modifying the previous example, we would have:

String myString = null;
int length = 0;
if (myString != null) {
    length = myString.length(); // Safe!
}

In this case, we only attempt to call the length() method if myString is not null, thereby avoiding the potential for a NullPointerException.

Moreover, as of Java 8, the Optional class was introduced to help deal with null references in a more explicit and safer manner. Using Optional can help you design more robust APIs by making the potential absence of a value explicit in your method signatures and forcing the caller to consider the null case. Here's an example of that :

Optional<String> myOptionalString = Optional.ofNullable(null);
int length = myOptionalString.map(String::length).orElse(0);

In this example, we wrap the potentially null string in an Optional. Optional.ofNullable is used to create an Optional that will either hold a non-null value or be empty if the provided value is null. Then, we use the map function to apply the length method only if the Optional is not empty. If the Optional is empty, which means the original string was null, the orElse method will be used to provide a default value of 0.

While null references cannot always be avoided, careful checks, liberal use of Optional, and thoughtful API design can prevent many common NullPointerExceptions, leading to more robust and error-resilient programs.

Improper Usage of equals() and hashCode()

Java's Object class, from which all other classes inherit, includes equals() and hashCode() methods. When you create custom classes in Java, you may need to override these methods, particularly if you're going to use instances of these classes as keys in a HashMap or inside a Set. Not doing so may lead to unexpected results.

The equals() method checks if two objects are equal at a logical level. By default, this method checks for reference equality, meaning it returns true only if the two references being compared point to the same object in memory. In many cases, you'll want to change this behavior to check for logical equality instead.

For example, consider a Interviewer class where equality should be determined by a interviewer's unique ID:

public class Interviewer {
    private String id;
    private String name;

    public Interviewer(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // Incorrect: uses reference equality, not logical equality
    // This equals method is inherited from the Object class
    //     // public boolean equals(Object obj) {
    //     return this == obj;
    // }

    // Correct: checks for logical equality using the unique ID
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        Interviewer interviewer = (Interviewer) obj;
        return id.equals(interviewer.id);    
    }
}

The hashCode() method, on the other hand, is used in collections like HashMap and HashSet for efficient data access. It should be overridden in such a way that if two objects are equal according to the equals() method, their hashCode() method should return the same value. If it doesn't, this can lead to unexpected and incorrect behavior when using these objects in collections.

Continuing the Interviewer class example:

    // Incorrect: does not override hashCode(), so it
    // uses the default one from Object class
    // which is not consistent with the overridden equals method
    // @Override
    // public int hashCode() {
    //    return super.hashCode();
    // }

    // Correct: hashCode() is consistent with equals()
    @Override
    public int hashCode() {
        return id.hashCode();
    }

By properly overriding equals() and hashCode(), you can ensure that your custom objects behave as expected when used in collections or when compared using equals(). Remember, when overriding one, you should override both. Otherwise, you might end up with hard-to-detect bugs and unexpected behavior.

Overcomplicating Solutions

Java offers various features, such as streams and optional. However, overuse can lead to unnecessarily complicated and hard-to-read code. In interviews, prioritize simplicity and readability over clever, complex solutions.

For example, a complex chain of stream operations might seem like a clever way to solve a problem in a single line. Still, it could also end up being confusing to the interviewer who's trying to follow your logic. Similarly, while we discuss the usefulness of Optional in the point above, excessive usage of Optional might clutter your code, making it harder to understand and maintain.

In a coding interview, your primary goal is to convey your problem-solving skills effectively. Your solutions should be easy to understand, correct, and efficient. Prioritize simplicity and readability over using advanced language features to create clever but complicated solutions. An interviewer will be more impressed by your ability to write clean, straightforward code that solves the problem efficiently, rather than a convoluted solution that makes heavy use of advanced language features.

Therefore, while it's beneficial to be familiar with advanced features in Java, it's crucial to use them judiciously and in a way that enhances, not obscures, your code's readability and simplicity.

How to Demonstrate Java Expertise in Interviews

To show off your Java expertise, you could delve deep into some of the core ideas behind the Java language, such as the JVM and memory management, or discuss the latest features in the recent JDK releases. Mention garbage collection where appropriate and be sure to use Java slang like describing objects as POJOs (Plain Old Java Objects).

Talk about how Java has evolved over the years and how the introduction of new features like lambda expressions, the Stream API, and the module system have changed the way you code in Java. If you've adopted a more functional style with these features or even have dabbled with Kotlin then it could be worth mentioning.

Remember to also follow Java best practices, such as preferring immutability, avoiding raw type usage, leveraging the enhanced for loop and try-with-resources statement, and properly overriding equals and hashCode when necessary.

Demonstrating your understanding of Java best practices and idioms is crucial to succeed in Java interviews. Don't get lost in niche Java trivia and focus on showing that you can code and not how to solve a problem without involving a whole ton of extra bells and whistles.

Java Interview Replays

Below you can find replays of mock interviews conducted on our platform in Java. The questions asked in these interviews tend to be language-agnostic (rather than asking about language-specific details and idiosyncrasies), but in these cases, the interviewee chose Java as the language they would work in.

About interviewing.io

interviewing.io is a mock interview practice platform. We've hosted over 100K mock interviews, conducted by senior engineers from FAANG & other top companies. We've drawn on data from these interviews to bring you the best interview prep resource on the web.

We know exactly what to do and say to get the company, title, and salary you want.

Interview prep and job hunting are chaos and pain. We can help. Really.