Cover image for Java interview prep: 15 Java interview questions

Java interview prep: 15 Java interview questions

cameronwilson profile image Cameron Wilson Updated on ・10 min read

Not all interviews will focus on algorithms and data structures — often times an interview will focus solely on the language or technology that you have claimed to be an expert in. In interviews like these, there usually aren’t any “gotcha” questions, instead they require you to draw on memory and your experience with the language — in other words, they test your knowledge of the programming language.

However, it can be easy to forget all the ins and outs of a language like Java, because simply put, we don’t deal with questions like “What kind of memory does the JVM manage?” and “Describe polymorphism with an example.” on a daily basis.

This post outlines some popular questions asked in a Java interview. Because Java specific questions can vary so much, this post acts as a guide to get you thinking about the different types of questions you can expect and what topics you should prepare for. If you’d like to see a complete guide to mastering the Java language, you can check out “The Definitive Java Interview Handbook”.

Today we’ll look at interview questions and answers related to:

  • The Java ecosystem
  • Java Classes
  • Interfaces
  • Inheritance
  • Multi-threading
  • Memory management
  • Collections
  • Exception handling
  • Serialization
  • Singleton

Let’s get started!

Q1: What is meant by Java being platform independent?

Java works on the principle of write once and run anywhere. Once a Java program is written, it is compiled into what is known as byte code, which can then be run on any Java Virtual Machine or JVM for short.

Alt Text

Compilation to bytecode is the magic behind Java’s interoperability. Different operating systems and hardware architectures have JVMs custom designed for themselves and all JVMs can run the same bytecode. Therefore, if you write a Java program on Linux, it will run seamlessly on a JVM designed for Windows operating system, making code agnostic to the underlying hardware and OS.

Q2: Explain the concepts of JRE, JDK, and JVM

  • JRE (Java Runtime Environment) includes the Java Virtual Machine and the standard Java APIs (core classes and supporting files.). The JRE contains just enough to execute a Java application, but not enough to compile it.

  • JDK (Java Development Kit) is the JRE plus the Java compiler, and a set of other tools to compile and debug code. JRE consists of Java platform libraries, Java Virtual Machine (JVM), Java Plugin and Java Web Start to run Java applications. JRE as a stand-alone does not contain compilers and debugging tools. If you need to develop Java programs you need the full Java SDK. The JRE is not enough for program development. Only the full Java SDK contains the Java compiler which turns your .java source files into bytecode .class files.

  • JVM (Java Virtual Machine) is an implementation of a specification, detailing the behavior expected of a JVM. Any implementation that conforms to the JVM specification should be able to run code compiled into Java bytecode irrespective of the language in which the code was originally written. In the Java programming language, all source code is first written in plain text files ending with the .java extension. Those source files are then compiled into .class files by the javac compiler. A .class file does not contain code that is native to your processor; it instead contains bytecodes — the machine language of the Java Virtual Machine. The java launcher tool then runs your application with an instance of the Java Virtual Machine.

Q3: How would you mark an entity package private in Java?

There’s no explicit modifier for package private. In the absence of any modifier the class or member variables are package private. A member marked package private is only visible within its own package. Consider the class below.

// class can be accessed by other classes within the same
// package but not outside of it.
class IamPackagePrivateClass {

   int IamPackagePrivate;
   private int IamPrivate;

   public IamPackagePrivate(int a, int b) {
      this.IamPackagePrivate = a;
      this.IamPrivate = b;

Package private is a slightly wider form of private. One nice thing about package-private is that you can use it to give access to methods you would otherwise consider private to unit test classes. So, if you use helper classes which have no other use but to help your public classes do something clients need, it makes sense to make them package private as you want to keep things as simple as possible for users of the library.

Q4: Why should you avoid the finalize() method in the Object class? What are some alternatives?

The Object class provides a callback method, finalize(), that may be invoked on an object when it becomes garbage. Object’s implementation of finalize() does nothing — you can override finalize() to do cleanup, such as freeing up resources.

The finalize() method may be called automatically by the system, but when it is called, or even if it is called, is uncertain. Therefore, you should not rely on this method to do your cleanup for you. For example, if you don’t close file descriptors in your code after performing I/O and you expect finalize() to close them for you, you may run out of file descriptors.

Here are some alternatives:

  • The try-with-resources idiom can be used to clean up objects. This requires implementing the AutoCloseable interface.
  • Using a PhantomReference to perform cleanup when an object is garbage collected
  • Using Cleaner class to perform cleanup actions.
  • Implement a close() method, which does the cleanup and document that the method be called.

Q5: Can you change the contents of a final array as shown in the code snippet below?

final int[] array = new int[5];
array[0] = 1;

It may appear counterintuitive, but we can actually change the contents of the array even though it is marked as final. The array variable points to a particular start location in the memory where the contents of the array are placed. The location or the memory address can’t be changed. For instance, the following code will not compile:

final int[] array = new int [5]
array = new int[10];

However, the following code will work.

public class FinalArrayExample {
  final int[] array = new int[5];

  // allowed
  void changeArrayContents(int i, int val) {
    array[i] = val;

  // not allowed and will not compile

  void changeArray() {
    array = new int [10]



Q6: Explain the difference between an interface and an abstract class? When should you use one or the other?

An abstract class can’t be instantiated, but it can be subclassed. An abstract class usually contains abstract and non-abstract methods that subclasses are forced to provide an implementation for.

An interface is a completely “abstract class” that is used to group related methods with empty bodies.

Following are four main differences between abstract classes and interfaces:

  • An abstract class can have final variables, static variables, or class member variables whereas an interface can only have variables that are final and static by default.

  • An abstract class can have static, abstract, or non-abstract methods. An interface can have static, abstract, or default methods.

  • Members of an abstract class can have varying visibility of private, protected, or public. Whereas, in an interface all methods and constants are public.

  • A class can only extend another class, but it can implement multiple interfaces. Similarly, an interface can extend multiple interfaces. An interface never implements a class or an interface.

Use an abstract class when subclasses share state or use common functionality. Or you require to declare non-static, non-final fields or need access modifiers other than public.

Use an interface if you expect unrelated classes would implement your interface. For example, the interfaces Comparable and Cloneable are implemented by many unrelated classes. Interfaces are also used in instances where multiple inheritance of type is desired.

Q7: What is polymorphism? Can you give an example?

Polymorphism is the ability in programming to present the same interface for differing underlying forms or data types. Polymorphism is when you can treat an object as a generic version of something, but when you access it, the code determines which exact type it is and calls the associated code. What this means is that polymorphism allows your code to work with different classes without needing to know which class it’s using.

Polymorphism is used to make applications more modular and extensible. Instead of messy conditional statements describing different courses of action, you create interchangeable objects that you select based on your needs. That is the basic goal of polymorphism.

The classic example of polymorphism is a Shape class. We derive Circle, Triangle, and Rectangle classes from the parent class Shape, which exposes an abstract method draw(). The derived classes provide their custom implementations for the draw() method. Now it is very easy to render the different types of shapes all contained within the same array by calling the draw() method on each object. This saves us from creating separate draw methods for each shape e.g. drawTriangle(), drawCircle() etc.

Alt Text

Q8: Can the main method be overloaded?

Yes, the main method, which is a static method, can be overloaded. But only public static void main(String[] args) will be used when your class is launched by the JVM even if you specify one or two command-line arguments. However, programmatically one can invoke the overloaded versions of the main method.

Q9: How can you pass multiple arguments to a method on each invocation call?

We can pass variable number of arguments to a method using varargs feature. Below is an example of passing multiple arguments of the same type to a method.

public void childrenNames(string... names) {
   for(int i= 0; i < names.length; i++)

  • The type name is followed by three dots, a space, and then the variable name.
  • The varargs variable is treated like an array.
  • The varargs variable must appear at the last in the method signature.
  • As a consequence of the above, there can only be a single varargs in a method signature.

The above method can be invoked as follows: Invoking Varargs Method

childrenNames("Jane", "Tom", "Peter");

Q10: Can a semaphore act as a mutex?

A semaphore can potentially act as a mutex if the number of permits it can give out is set to 1. However, the most important difference between the two is that in the case of a mutex, the same thread must call acquire and subsequent release on the mutex whereas in the case of a binary semaphore, different threads can call acquire and release on the semaphore.

This leads us to the concept of “ownership”. A mutex is owned by the thread acquiring it, till the point, it releases it, whereas for a semaphore there’s no notion of ownership.

Need a refresher on multithreading? Check out this article “Java Multithreading and Concurrency: What to know to crack a senior engineering interview”.

Q11: Explain the Externalizable interface

The Serializable interface gets us automatic serialization capability for objects of our class. On the other hand the Externalizable interface provides a way to implement a custom serialization mechanism. A class that implements the Externalizable interface is responsible to save and restore the contents of its own instances.

The Externalizable interface extends the Serializable interface and provides two methods to serialize and deserialize an object, writeExternal() and readExternal().

Q12: If a code block throws more than one exception, how can it be handled?

Multiple types of exceptions thrown by a snippet of code can be handled by multiple catch block clauses followed by the try block. An example snippet of exception handling appears below:

void process(int val)  {
   try {
        if (val == 1)
            //checked exception
            throw new FileNotFoundException();

        if (val == 2)
            // runtime exception
            throw new NullPointerExxception();

        if (val == 3)
            // error exception
            throw new StackOverflowError

   } catch (RuntimeException re) {
            // catches all unchecked  exceptions

   } catch (Exception e) {
            // catches all checked exceptions

   } catch (Error err) {
            // catches all errors



Q13: If you were to use a set, how would you determine between a HashSet and a TreeSet?

Initially, you may want to use HashSet as it will give you a better time complexity, but it makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.

So if you are wanting to maintain the order it’s best to use a TreeSet as it stores keys in ascending order rather than in their insertion order. It’s not thread safe. However, keep in mind that TreeSet is not thread safe whereas a HashSet is.

Q14: What are a few ways you can improve the memory footprint of a Java application?

Here are three key steps you can take to improve the memory footprint:

  • Limiting the scope of local variables. Each time the top scope from the stack is popped up, the references from that scope are lost, and this could make objects eligible for garbage collection.
  • Explicitly set variable references to null when not needed. This will make objects eligible for garbage collection.
  • Avoid finalizers. They slow down program performance and do not guarantee anything.

Q15: What is the best way to implement a singleton class?

The best way to implement a singleton as per Josh Bloch is to use an enum type for the singleton. Because Java ensures that only a single instance of an enum is ever created, the singleton class implemented via enums is safe from reflection and serialization attacks.

class Demonstration {
    public static void main( String args[] ) {
        Superman superman = Superman.INSTANCE;

enum Superman {

    private final String name = "Clark Kent";
    private String residence = "USA";

    public void fly() {
        System.out.println("I am flyyyyinggggg ...");

Gaining a mastery

There has been a lot covered in this post about the Java programming language, ranging from the Java ecosystem (question1) to multi-threading (question 10) and exceptions (question 12). These are the types of Java interview questions you can expect. It’s best to use the material outlined above as a guideline for topics you’ll want to study and the types of questions you can expect.

However, the material here just scratches the surface. There are many more concepts to revisit or explore like object-oriented programming, static variables, and method overloading. If you’d like to take a deep dive into Java and explore hundreds of more questions about the topics mentioned above then “The Definitive Java Interview Handbook” is going to be a great resource to get you refreshed on the core java principles — everything from the basics to more advanced functionality.

Happy learning!

Posted on by:

cameronwilson profile

Cameron Wilson


Writer of all things programming


Level up on in-demand tech skills - at your own speed. Text-based courses with embedded coding environments help you learn without the fluff.


Editor guide

Good summary. One small typo: in Q9, you accidentally switched to lower case for the system class (-> System). In Q3, one could argue that upper case variable names are discouraged (even though it‘s valid syntax).


A good remark, I think the same goes for the string class.


Q14: memory usage

A big one in web applications is to prefer streaming over storing everything in RAM.

For example:

str = readEntireFileIntoString()



Huge huge differences in RAM usage (O(N) vs O(1) ). Streaming is by far preferable.

In XML for example, SAX parsers are far preferable over DOM parsers because the later store everything in RAM. If your file is huge you'll hog a lot of RAM.

Another source of horror is careless use of relations in Hibernate. If you're careless you can end up storing the entire database for each object and hogging the RAM like mad.


I mention these.. because sadly, I've seen them in the wild.


Q15 is a trick question. The best way to implement it is: not at all. Singleton is the only design pattern which is also an anti-pattern.

I often ask this question during interviews: if they know design patterns, know singleton, and what they think of it. If they don't mention it being an anti-pattern. I ask them if they can explain why I think it's an anti-pattern.

The answer is rather simple. The whole purpose of a singleton is to introduce global state, and global state is bad.


Well, global state appears in the real world. Like configurations files, db connection parameters, etc..
One advantage of using factory methods + singletons is that if for whatever reason you don't want it to be a singleton any more, there's no obligation that it continue to be a singleton.

I'm not sure that making sure its a single instance in the VM is the most overriding principle. I haven't had much need for that guarantee. More important is the abstraction of returning an object that represents some configuration. Which over time could become several objects (for example when you make your app multi-tenant)


Of course global state appears in the real world. Just like memory leaks, concurrent modifications, and a whole lot of other bad things.

Configuration files and DB connection parameters do not have to be global state. You should pass them along as context or local state. Within a context (like Spring's ApplicationContext) you can have a single instance of an object. It is much like a singleton, except that it is possible to have a completely different instance of that object in a different context. Via the context you can get the proper instance of that object.

Depending on a global state is also problematic with unit testing.

It is simply best to avoid using singletons. However it is not always possible without creating a bigger mess. The prime example would be logging.

I see. I guess that's the heart and soul of the IOC / Hollywood principle. Rather than have your methods calling global classes for something, they should have those set for them so its easier to test.


Q4: avoiding finalize()

Besides the issue of non-determinism of the finalize().. a bigger reason to avoid the finalize() method is that modern garbage collectors are copy collectors. Meaning that if 80% of your objects are garbage, only 20% gets processed by the system. The other 80% simply disappears with no further processing.

This is a huge advantage over other schemes. Because with copy collectors processing garbage is almost free. However, if you add finalizers all over the place, then the GC needs to go through all the finalizers() hence you lose all the advantages of copy collection in terms of speed. This ends up being a huge performance hit.


Q13: HashSet vs TreeSet

You claim HashSet is thread safe. But the Javadocs say otherwise:

"Note that this implementation is not synchronized. If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the set. If no such object exists, the set should be "wrapped" using the Collections.synchronizedSet method. This is best done at creation time, to prevent accidental unsynchronized access to the set:

Set s = Collections.synchronizedSet(new HashSet(...));

For multi-threading I highly recommend java.util.concurrent rather than synchronizedSet(). But no, its very dangerous to use HashSet in multithreaded situations. In fact, in the past there were cases where HashSet's cousin, HashMap hung even when there were only reads across threads.

The main difference is that HashSets use a hash table implementation whereas the TreeSet uses some binary tree implementation. There are many differences such as the ones you mentioned. Other differences include:

  • Hash tables use an O(1) algorithm except when hashes collide when it can degrade to O(N) if we're not careful. Of course, they aren't immune to memory paging
  • Balanced binary tree implementations (such as red black trees used in Java) are O(logN) so they have very good performance as well but less than Hash tables
  • Hash tables are contiguous in memory so they can take advantage of spatial locality in the CPU
  • Binary trees are not contiguous so the memory may be all over RAM meaning that its unlikely that reading one node would lead to reading the next one. So you'll probably get a lot more cache misses (even though you may get some advantages in terms of temporal locality in the cache)

In practice I've used TreeSet as an alias of "ordered list with no repetitions". HashSet I've used to check what has been processed vs what still needs to be processed.

But often I just end up using a Map instead. They're often more useful.