When interviewing for a role that uses Java, the interviewer will often ask broad questions to make sure you actually understand the language before you get offered the role.
What like?
Well, that's what we're going to cover in this guide. Everything from beginner basics to more nuanced and advanced questions, so you can read along, check what you know, then ace your interview with ease!
Ready? Let’s dive in.
Sidenote: If you find that you’re struggling with the questions in this guide want to build some more impressive projects for your portfolio, then check out my Java programming bootcamp:
Updated for 2026, you'll learn Java programming fundamentals all the way from complete beginner to advanced skills. Better still, you’ll be able to reinforce your learning with over 80 exercises and 18 quizzes.
This is the only course you need to go from complete programming beginner to being able to get hired as a Java Developer!
With that out of the way, let’s get into these interview questions!
Beginner Java coding interview questions
These questions focus on the fundamentals of Java, covering essential concepts that every Java developer should know.
Mastering these basics not only sets the stage for more advanced topics but also demonstrates to interviewers that you have a solid grasp of the language.
#1. What are the main features of Java?
Java remains one of the most popular programming languages, thanks to its powerful features and consistent updates that keep it relevant. These key features include:
Platform Independence. Java's "Write Once, Run Anywhere" (WORA) principle ensures that bytecode compiled on one platform can run on any other platform with a Java Virtual Machine (JVM)
Object-Oriented Programming (OOP). Java adheres to OOP principles such as encapsulation, inheritance, polymorphism, and abstraction. This design makes code modular, reusable, and easy to maintain
Robustness. Java minimizes runtime errors through features like automatic memory management, garbage collection, and strong exception handling
Security. Java's security manager and classloader provide mechanisms to restrict untrusted code, protecting sensitive resources
Multithreading. Java's built-in multithreading support allows simultaneous execution of multiple tasks, improving performance in applications requiring parallel processing. Virtual threads, introduced in Java 21, make multithreading lighter and more efficient by reducing the overhead of traditional threads
Rich Standard Library. Java's extensive standard library supports operations ranging from data structure manipulation to advanced networking and concurrency
High Performance. The Just-In-Time (JIT) compiler improves runtime performance by optimizing bytecode execution and applying techniques like inlining and loop unrolling
Scalability. Java scales seamlessly from small applications to large enterprise systems, making it a strong choice for high-performance systems
Why would an interviewer ask this?
This question tests whether you have a solid grasp of what makes Java distinctive as a language. Interviewers use it to gauge your overall familiarity with the platform before moving into more specific topics.
#2. What is the Java Virtual Machine (JVM), and how does it work?
The Java Virtual Machine (JVM) is a runtime engine that allows Java programs to execute on any device with a JVM implementation. It is central to Java's "Write Once, Run Anywhere" (WORA) promise by abstracting platform-specific details and providing a consistent execution environment.
Here's how the JVM operates:
Compilation. Java source code (.java files) is compiled into platform-independent bytecode (.class files) by the Java Compiler (javac)
Class Loading. The JVM dynamically loads the required bytecode into memory during execution
Bytecode Verification. The verifier checks the bytecode for memory safety and security, ensuring it adheres to Java's strict standards
Execution. The bytecode is executed by either interpreting it line by line or compiling it to native machine code using the Just-In-Time (JIT) compiler for performance optimization
Why would an interviewer ask this?
The JVM is the foundation on which everything else in Java runs on, which is why interviewers sometimes ask this to confirm you understand how Java code actually executes, and not just how to write it.
#3. What are the key differences between the JVM, JRE, and JDK?
These three acronyms get used a lot in Java, and while they're related, they each refer to something different. Understanding the distinction matters when setting up a Java environment, troubleshooting issues, or explaining your development setup to a team.
Java Virtual Machine (JVM)
The JVM is the runtime engine that actually executes your Java code. It takes the compiled bytecode and runs it, handling memory management, garbage collection, and security along the way. The JVM is what makes Java platform-independent, since the same bytecode can run on any machine that has a JVM installed.
When you run a Java program with java MyProgram, it's the JVM that loads and executes the bytecode in MyProgram.class.
Java Runtime Environment (JRE)
The JRE is everything you need to run a Java application. It includes the JVM plus the core libraries and supporting files that Java programs depend on. If you just want to run Java applications without writing or compiling any code, the JRE is all you need.
Java Development Kit (JDK)
The JDK is the full toolkit for Java developers. It includes everything in the JRE, plus the tools you need to write and compile Java code. Most importantly, the Java compiler (javac), as well as utilities like the debugger (jdb) and the archiver (jar).
// Compiling a program requires the JDK
javac MyProgram.java
// Running it only requires the JRE (or JDK, which includes the JRE)
java MyProgramA simple way to remember it is that the JVM runs code, the JRE runs applications, and the JDK builds them.
Why would an interviewer ask this?
This is a common early interview question that tests whether you understand the Java ecosystem beyond just writing code. Interviewers want to see that you know what's actually happening when you compile and run a Java program.
#4. What is the difference between == and .equals() in Java?
In Java, == and .equals() are both used for comparison but serve distinct purposes:
== compares the memory addresses of objects (reference comparison), but for primitives, it compares values.
For example
int a = 5;
int b = 5;
System.out.println(a == b); // true (values are the same)
String str1 = new String("Java");
String str2 = new String("Java");
System.out.println(str1 == str2); // false (different memory addresses)While .equals() compares the logical equality or content of two objects. So by default, the .equals() method in the Object class behaves like ==, but many classes (e.g., String) override it to compare content.
For example
System.out.println(str1.equals(str2)); // true (contents are the same)Why would an interviewer ask this?
This tests your understanding of Java's object model and equality principles. Misusing == instead of .equals() is a common source of subtle bugs, especially when comparing strings or objects fetched from a database or API.
#5. What are Java’s access modifiers, and how are they used?
Access modifiers define the scope and accessibility of classes, methods, and properties. Java provides four main access modifiers:
public. Accessible from any class
protected. Accessible within the same package and by subclasses outside the package
default (package-private). Accessible only within the same package (no keyword needed)
private. Accessible only within the same class
For example
package accessmodifiers;
public class Example {
public int publicVar = 1;
protected int protectedVar = 2;
int defaultVar = 3; // No modifier specified
private int privateVar = 4;
public void show() {
System.out.println(privateVar); // Accessible within the same class
}
}Why would an interviewer ask this?
Access modifiers are fundamental to encapsulation and secure code design. Interviewers test this to see if you understand how to control access effectively, especially when designing APIs or structuring large applications.
#6. What is the difference between final, finally, and finalize in Java?
These three terms sound similar but have distinct purposes in Java.
Final
Final is a keyword used to declare constants, prevent method overriding, or prevent inheritance of a class. For example, a final variable cannot be changed once assigned:
For example
final int maxValue = 100;
maxValue = 200; // Error: cannot assign a value to final variableA final method cannot be overridden by subclasses, and a final class cannot be extended by other classes (e.g., java.lang.String).
Finally
Finally is a block in exception handling that executes after the try-catch block, regardless of whether an exception was thrown.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Caught an exception");
} finally {
System.out.println("This will always execute");
}Finalize
Finalize is a method called by the Garbage Collector before an object is destroyed. It has been deprecated since Java 9 and is scheduled for removal in a future release due to serious concerns around security, performance, and reliability. Developers should use try-with-resources or the Cleaner API instead for resource cleanup.
Why would an interviewer ask this?
These three keywords are easy to confuse but serve very different purposes. Interviewers ask this to test whether you understand Java's approach to constants and inheritance (final), exception handling (finally), and why finalize should be avoided in modern Java.
#7. What is the difference between an abstract class and an interface?
Both abstract classes and interfaces are used to achieve abstraction in Java, but they differ in design and use cases.
Abstract class
An abstract class can have both abstract methods (without implementation) and concrete methods (with implementation). It supports fields with any access modifier, and a class can only extend one abstract class.
abstract class Animal {
abstract void sound(); // Abstract method
void sleep() { // Concrete method
System.out.println("Sleeping...");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}Interface
All methods in an interface are public and abstract by default. From Java 8 onwards, interfaces can also include default methods (with implementation) and static methods. From Java 9, interfaces can include private methods to enhance code organisation. A class can implement multiple interfaces.
interface Pet {
void play();
default void info() {
System.out.println("This is a pet.");
}
}
class Cat implements Pet {
public void play() {
System.out.println("The cat is playing.");
}
}When to use each
Use an abstract class when you need a base class with shared state or behaviour for related classes. Use an interface when defining a contract that multiple unrelated classes can implement.
Why would an interviewer ask this?
This is a classic OOP question that tests your understanding of abstraction and code design. Interviewers want to see if you know not just the technical differences, but when to choose one over the other in a real-world scenario.
#8. What are records in Java?
Records, introduced in Java 16, are a special type of class designed for storing immutable data. They reduce boilerplate code by automatically generating common methods like equals(), hashCode(), and toString() based on the fields you declare.
For example, this single line replaces what would traditionally require a full class with private fields, a constructor, getters, and the above methods written manually.
public record Person(String name, int age) {}Handy right?
And because records use value-based equality, two instances with the same data are considered equal even if they are different objects in memory:
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
System.out.println(p1.equals(p2)); // trueThis makes records particularly useful for DTOs (Data Transfer Objects), API responses, and other scenarios where you need a simple, immutable data carrier without complex behaviour.
Why would an interviewer ask this?
Records are now a staple of modern Java development. Interviewers ask this to see if you're up to date with the language and understand when to use a record over a traditional class.
#9. What is a constructor in Java, and how does it differ from a method?
When you create an object in Java, something needs to set it up, assign its initial values, prepare its state, and get it ready to use. That's exactly what a constructor does.
A constructor is a special block of code that runs automatically when an object is created. It has the same name as the class and no return type, not even void.
class Person {
String name;
// Constructor
Person(String name) {
this.name = name;
}
}So when you write new Person("Alice"), the constructor runs immediately, setting the name to "Alice" before you ever use the object.
Methods, on the other hand, are blocks of code you call explicitly to perform actions on an object. They must declare a return type, and unlike constructors they can be static, abstract, or final.
class Person {
String name;
Person(String name) {
this.name = name;
}
// Method
void greet() {
System.out.println("Hello, my name is " + name);
}
}Here, greet() is a method that needs to be called manually, so it won't run on its own when the object is created.
Why would an interviewer ask this?
Frameworks like Spring and Hibernate rely heavily on object initialisation behind the scenes, and knowing how constructors work helps you understand why those frameworks behave the way they do.
Interviewers ask this to check you have a solid grasp of how objects come to life in Java, not just how to write code that uses them.
#10. What is the difference between String, StringBuilder, and StringBuffer in Java?
When working with text in Java, you have three main options and choosing the wrong one can cause performance issues or thread-safety bugs.
String
A String in Java is immutable, meaning its value cannot be changed once created. Any operation that modifies a String creates a new object rather than changing the existing one. This makes String thread-safe by default, but can cause performance overhead when performing many concatenations.
String str = "Hello";
str = str + " World"; // A new String object is createdStringBuilder
StringBuilder is mutable, meaning its value can be changed without creating new objects. This makes it much faster than String for repeated text manipulation. However, it is not thread-safe, so it should only be used in single-threaded environments.
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // Modifies the existing objectStringBuffer
StringBuffer works the same way as StringBuilder but is thread-safe, using synchronized methods to ensure safe operation across multiple threads. This synchronization adds overhead, making it slower than StringBuilder in single-threaded scenarios.
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // Modifies the existing objectTL;DR: Use String for immutable text, StringBuilder for fast single-threaded manipulation, and StringBuffer when you need thread-safe text manipulation.
Why would an interviewer ask this?
This tests your understanding of immutability and thread safety in Java. Interviewers want to see if you can identify the right tool for the job rather than defaulting to String for everything.
#11. What is a static method in Java, and when would you use it?
In Java, every method you write belongs somewhere. Most methods belong to an instance of a class, meaning you need to create an object before you can call them. But sometimes a method doesn't need any object state at all; it just takes some inputs and returns an output.
That's where static methods come in.
A static method belongs to the class itself rather than any instance of it, which means you can call it directly without creating an object first.
class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
// Call the method without creating an object
int sum = MathUtils.add(5, 10);Because static methods aren't tied to an instance, they can only access static fields and other static methods directly. They also cannot use this or super keywords.
You'll see static methods used extensively in utility classes like Java's built-in Math class, where operations like Math.max() and Math.sqrt() don't need any object state to work. They're also common in factory methods, helper classes, and constants.
The key question to ask yourself is: does this method need to know anything about the state of a specific object? If the answer is no, a static method is likely the right choice.
Why would an interviewer ask this?
Static methods are a fundamental part of Java design, so interviewers ask this to see if you understand the difference between class-level and instance-level behaviour and can make the right design decision about when to use each.
Intermediate Java coding interview questions
Now that we’ve covered the fundamentals, it’s time to dive into the intermediate-level concepts that test your ability to work with more complex data structures, concurrency, and other essential Java features.
These questions are designed to assess how well you can handle real-world scenarios, optimize your code, and leverage Java’s advanced features effectively.
#12. What are the key differences between Array and ArrayList in Java?
Arrays and ArrayLists both store collections of data in Java, but they work quite differently under the hood.
An array is a fixed-size data structure built into the Java language. Once you define its size, it cannot grow or shrink. It can store both primitives and objects, and because of its simplicity, it tends to be faster.
int[] numbers = new int[5];
numbers[0] = 10;
System.out.println(numbers[0]);An ArrayList is part of the Java Collections framework and gives you a resizable array that grows and shrinks dynamically as you add or remove elements.
It comes with built-in methods that make working with data much more convenient, but it can only store objects, not primitives.
import java.util.ArrayList;
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10);
System.out.println(numbers.get(0));The right choice depends on your situation. If you know the size upfront and need raw performance, use an array. If you need flexibility and the convenience of built-in methods like add(), remove(), and contains(), use an ArrayList.
Why would an interviewer ask this?
This tests whether you understand the trade-offs between performance and flexibility when choosing data structures. Interviewers want to see that you can make an informed decision rather than defaulting to one or the other.
#13. How does the Collections framework work, and what are its key components?
The Java Collections framework is a set of classes and interfaces that provides ready-made data structures and algorithms for storing and manipulating groups of objects. However, rather than building your own list, set, or map from scratch, the framework gives you well-tested, optimised implementations to work with.
So let's break them down.
List
A List is an ordered collection that allows duplicate elements. It's what you'd use when order matters, or you need to access elements by index. The most common implementation is ArrayList.
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
System.out.println(names); // Output: [Alice, Bob]Set
A Set is an unordered collection that does not allow duplicate elements. Use it when you need to ensure uniqueness. The most common implementation is HashSet.
Set<Integer> uniqueNumbers = new HashSet<>();
uniqueNumbers.add(1);
uniqueNumbers.add(1);
System.out.println(uniqueNumbers); // Output: [1]Map
A Map stores key-value pairs, where each key is unique. Use it when you need to look something up by a specific identifier. The most common implementation is HashMap.
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 30);
ageMap.put("Bob", 25);
System.out.println(ageMap); // Output: {Alice=30, Bob=25}Queue and Deque
A Queue is designed for holding elements prior to processing, following a first-in, first-out (FIFO) order. A Deque supports both FIFO and last-in, first-out (LIFO) operations. Common implementations include LinkedList and ArrayDeque.
Why would an interviewer ask this?
The Collections framework is used in almost every Java application. Interviewers ask this to see if you understand the core data structures available to you and can choose the right one for a given problem.
#14. What is the difference between HashMap, LinkedHashMap, and TreeMap?
All three are implementations of the Map interface in Java, meaning they all store key-value pairs. The difference is in how they order those pairs and how that affects performance.
HashMap
HashMap is the fastest of the three for most operations like insertion and retrieval. It stores entries in a hash table, which means the order of elements is not guaranteed. It allows one null key and multiple null values.
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Alice", 30);
hashMap.put("Bob", 25);
System.out.println(hashMap); // Output: {Bob=25, Alice=30} (order not guaranteed)LinkedHashMap
LinkedHashMap extends HashMap and maintains a linked list of entries, preserving the order in which they were inserted. It is slightly slower than HashMap due to the overhead of maintaining that linked list.
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Alice", 30);
linkedHashMap.put("Bob", 25);
System.out.println(linkedHashMap); // Output: {Alice=30, Bob=25} (insertion order maintained)TreeMap
TreeMap stores entries in sorted order based on the natural ordering of keys, or a custom comparator if provided. It does not allow null keys and is slower than the other two due to its underlying Red-Black Tree structure.
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Bob", 25);
treeMap.put("Alice", 30);
System.out.println(treeMap); // Output: {Alice=30, Bob=25} (keys sorted alphabetically)In short, use HashMap when order doesn't matter, and you need speed, LinkedHashMap when you need to preserve insertion order, and TreeMap when you need entries sorted by key.
Why would an interviewer ask this?
Choosing the right Map implementation can have a significant impact on performance and behaviour. Interviewers ask this to see if you understand the trade-offs and can select the right tool for a given scenario.
#15. How does exception handling work in Java?
Exception handling in Java is how you manage unexpected situations at runtime without crashing your program. Rather than letting an error bring everything down, Java gives you tools to catch problems, handle them gracefully, and clean up after them.
Checked and unchecked exceptions
Checked exceptions are caught at compile time, meaning Java will refuse to compile your code unless you handle them or declare them with the throws keyword. Examples include IOException and SQLException.
Unchecked exceptions are not checked at compile time and usually indicate programming errors, such as NullPointerException or ArithmeticException.
Try-catch
A try block contains code that might throw an exception. If it does, the catch block handles it.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
}Finally
The finally block always runs regardless of whether an exception was thrown or caught. It's typically used for cleanup operations like closing connections or releasing resources.
try {
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
} catch (IOException e) {
System.out.println("File not found");
} finally {
System.out.println("Cleanup code here");
}Throw and throws
throw is used to explicitly throw an exception, while throws declares that a method may throw a particular exception.
void divide(int a, int b) throws ArithmeticException {
if (b == 0) throw new ArithmeticException("Cannot divide by zero");
System.out.println(a / b);
}Why would an interviewer ask this?
Exception handling is fundamental to writing robust, fault-tolerant Java applications. Interviewers ask this to see if you can write clean error-handling logic and manage resources effectively without letting unexpected situations crash your program.
#16. What are generics in Java, and why are they used?
Before generics were introduced in Java 5, collections could hold any type of object, which meant you had no compile-time safety and had to cast objects manually every time you retrieved them.
However, generics solved this by letting you specify the type a collection or method works with upfront, so Java can catch type mismatches before your code ever runs.
For example
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Generics");
// No casting needed
String firstItem = list.get(0);Without the <String> type parameter, you'd have to cast the result of list.get(0) to a String manually, and if the wrong type was in the list you'd only find out at runtime.
Generics can also be applied to methods, making them work with any type while still being type-safe.
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}You can also restrict what types are allowed using bounded type parameters. For example, this method only accepts numbers:
public <T extends Number> void printNumber(T number) {
System.out.println("Number: " + number);
}Why would an interviewer ask this?
Generics are central to Java's Collections framework and appear throughout modern Java code. Interviewers ask this to see if you understand type safety and can write reusable, robust code without relying on casting or risking runtime errors.
#17. How does Java implement threads and concurrency?
Concurrency in Java is about making your program do more than one thing at a time. Java gives you several ways to achieve this, from low-level thread management to high-level abstractions that handle the complexity for you.
Creating threads
The two most basic ways to create a thread are extending the Thread class or implementing the Runnable interface.
// Extending Thread
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
MyThread thread = new MyThread();
thread.start();// Implementing Runnable
class MyTask implements Runnable {
public void run() {
System.out.println("Task is running");
}
}
Thread thread = new Thread(new MyTask());
thread.start();Concurrency utilities
For most real-world applications, manually creating and managing threads is too low-level. The java.util.concurrent package provides higher-level tools that are safer and easier to work with.
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Task 1"));
executor.execute(() -> System.out.println("Task 2"));
executor.shutdown();Other useful utilities include ReentrantLock for explicit locking, ConcurrentHashMap for thread-safe collections, and synchronization helpers like CountDownLatch and Semaphore.
Why would an interviewer ask this?
Threads and concurrency are among the most commonly tested topics in Java interviews. Interviewers ask this to see if you understand how Java handles parallel execution and whether you know when to use low-level threads versus higher-level abstractions.
#18. What are virtual threads in Java?
Traditional Java threads map one-to-one with operating system threads. This means every thread you create consumes significant memory and system resources, which puts a hard limit on how many you can run at once. In a high-traffic web server handling thousands of simultaneous requests, this becomes a real bottleneck.
Virtual threads, introduced as a standard feature in Java 21, solve this problem. They are lightweight threads managed by the JVM rather than the operating system, meaning you can create millions of them without the memory overhead of traditional threads.
// Creating a virtual thread
Thread.startVirtualThread(() -> {
System.out.println("Running in a virtual thread");
});You can also use the familiar ExecutorService API with virtual threads:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Task running in virtual thread"));
}The key advantage is that when a virtual thread performs a blocking operation like a database query or HTTP request, the JVM suspends it and frees the underlying OS thread for other work. This makes virtual threads particularly well suited for I/O-bound workloads like web servers or microservices.
It is worth noting that virtual threads are not a replacement for everything. For CPU-intensive tasks, traditional threads or parallel streams are still the better choice since the bottleneck is processing power rather than waiting on I/O.
Why would an interviewer ask this?
Virtual threads are one of the most significant additions to Java in recent years and are showing up in interviews at companies running Java 21 or planning to upgrade. Interviewers ask this to see if you are keeping up with the language and understand the concurrency model well enough to know when virtual threads help and when they don't.
#19. What is the difference between synchronized blocks and methods in Java?
When multiple threads access shared data at the same time, you can end up with unpredictable results. Synchronization is how Java prevents this by ensuring only one thread can execute a particular section of code at a time.
Even better, you can apply synchronization at two levels by using the whole method or just a specific block of code. So let's break them down.
Synchronized methods
A synchronized method locks the entire method, preventing any other thread from entering it until the current thread is done.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}Synchronized blocks
A synchronized block lets you lock only a specific section of code rather than the entire method. This is more efficient because other threads can still execute the non-synchronized parts of the method while one thread holds the lock.
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}Synchronized blocks also give you more flexibility because you can lock on any object, not just this, which is useful when you need finer control over which threads block each other.
However, as a general rule, prefer synchronized blocks over synchronized methods. They minimize the amount of code that runs under a lock, which reduces contention between threads and improves performance.
Why would an interviewer ask this?
This tests whether you understand not just how synchronization works, but how to use it efficiently. Interviewers want to see that you can write thread-safe code without over-locking and causing unnecessary performance bottlenecks.
#20. How does Java handle file I/O?
At some point in almost every Java application, you need to read from or write to a file. Java gives you two ways to do this, each suited to different situations.
java.io
The java.io package is the traditional approach. It uses classes like BufferedReader and BufferedWriter which wrap around lower-level file readers and writers to make operations more efficient by buffering data in memory.
// Reading a file
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}// Writing to a file
try (BufferedWriter writer = new BufferedWriter(new FileWriter("example.txt"))) {
writer.write("Hello, Java I/O!");
} catch (IOException e) {
e.printStackTrace();
}Notice the use of try-with-resources here. This ensures the file is automatically closed when you're done, even if an exception is thrown, which is the recommended way to handle file operations in modern Java.
java.nio
The java.nio package, introduced in Java 7, offers a cleaner and more concise alternative through the Files and Paths classes. It reduces boilerplate significantly and performs better with large files.
// Reading all lines
List<String> lines = Files.readAllLines(Paths.get("example.txt"));
lines.forEach(System.out::println);// Writing to a file
Files.write(Paths.get("example.txt"), Arrays.asList("Hello, Java NIO!"));For most modern applications, java.nio is the preferred choice due to its simpler API and better performance characteristics.
Why would an interviewer ask this?
File I/O is a fundamental skill for any Java developer. Interviewers ask this to see if you're familiar with both approaches, understand when to use each, and know how to handle resources safely using try-with-resources.
#21. What is the purpose of the volatile keyword in Java?
When multiple threads run at the same time, Java allows each thread to keep a local copy of a variable in its own cache for performance reasons. This is usually fine, but it can cause problems when one thread updates a variable and another thread keeps reading the stale cached value without seeing the change.
The volatile keyword solves this by telling the JVM never to cache the variable locally. Every time a thread reads a volatile variable, it goes directly to main memory, and every time it writes to one, the update is immediately visible to all other threads.
// Without volatile - the loop may never stop
class Counter {
private boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// Another thread calling stop() may not be seen here
}
}
}// With volatile - the update is immediately visible to all threads
class Counter {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// Loop will stop as soon as stop() is called
}
}
}It's important to understand what volatile does not do, though, in that it guarantees visibility but not atomicity.
This means it's suitable for simple flags or status variables that are read and written by multiple threads, but not for compound operations like incrementing a counter. For those, you should use synchronized or the AtomicInteger class instead.
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Atomic and thread-safe
}
public int getCount() {
return count.get();
}
}Why would an interviewer ask this?
volatile is one of those topics that separates developers who have a surface-level understanding of threading from those who truly understand how Java manages memory across threads.
Interviewers ask this to see if you know when it's sufficient and when you need stronger guarantees like synchronization or atomic classes.
#22. What is an inner class, and what are the types of inner classes in Java?
In Java, you can define a class inside another class. These are called inner classes, and they're useful when one class is so closely tied to another that it doesn't make sense for it to exist independently. Inner classes can access the private members of their enclosing class directly, which makes them a powerful tool for encapsulation.
There are four types of inner classes in Java.
Nested static class
A nested static class is declared with the static keyword inside another class. Because it's static, it doesn't need an instance of the enclosing class to be created, and it cannot access the enclosing class's non-static members.
class Outer {
static class StaticNested {
void display() {
System.out.println("Inside static nested class");
}
}
}
Outer.StaticNested nested = new Outer.StaticNested();
nested.display();Non-static inner class
A non-static inner class is tied to an instance of its enclosing class and can access all of its members, including private ones.
class Outer {
class Inner {
void display() {
System.out.println("Inside non-static inner class");
}
}
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();Local inner class
A local inner class is defined inside a method and is only accessible within that method. It's useful when you need a helper class for a specific operation and don't want it to be visible anywhere else.
class Outer {
void method() {
class LocalInner {
void display() {
System.out.println("Inside local inner class");
}
}
LocalInner local = new LocalInner();
local.display();
}
}Anonymous inner class
An anonymous inner class is a one-off class that is defined and instantiated in a single statement. It's commonly used to implement interfaces or extend classes inline without creating a named class.
interface Greeting {
void sayHello();
}
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from anonymous inner class");
}
};
greeting.sayHello();In modern Java, anonymous inner classes are often replaced by lambda expressions when implementing functional interfaces, which is cleaner and more concise.
Why would an interviewer ask this?
Inner classes come up in real-world Java code more often than you might expect, particularly in event handling, callbacks, and framework design. Interviewers ask this to see if you understand the different types, when each is appropriate, and how they relate to encapsulation.
Advanced Java coding interview questions
These questions focus on performance optimization, JVM internals, garbage collection, and other high-level topics.
Mastering these concepts not only helps you excel in interviews but also equips you to handle complex challenges in real-world applications.
#23. How does garbage collection work in Java?
One of Java's most important features is that you don't have to manually allocate and free memory like you do in languages such as C or C++.
Instead, Java handles this automatically through a process called garbage collection, where the JVM periodically identifies objects that are no longer being used and reclaims the memory they were occupying.
How memory is organised
To understand garbage collection, you first need to understand how Java organises memory. The heap is where all objects are stored, and it's divided into sections:
The Young Generation is where new objects are created. Most objects are short-lived and get collected here quickly
Objects that survive long enough get promoted to the Old Generation, which is collected less frequently
The Metaspace holds metadata about classes and sits outside the main heap
How the collection process works
When the garbage collector runs, it identifies objects that are no longer reachable from any active thread, meaning nothing in your program holds a reference to them anymore. It then frees that memory so it can be reused.
A Minor GC handles the Young Generation, while a Major GC or Full GC handles the Old Generation. Full GCs are more expensive and can cause noticeable pauses in your application, which is why minimising unnecessary object creation is an important performance consideration.
Types of garbage collectors
Java gives you several garbage collectors to choose from depending on your needs:
Serial GC. Single-threaded and simple, suited for small applications
Parallel GC. Uses multiple threads and prioritises throughput
G1 GC. The default for most applications, balancing throughput, and pause times
ZGC. Designed for ultra-low latency, keeping pauses under a few milliseconds even on very large heaps
Shenandoah GC. Similar goals to ZGC but designed for medium-sized heaps
Why would an interviewer ask this?
Garbage collection sits at the heart of Java's memory model and has a direct impact on application performance. Interviewers ask this to see if you understand how memory is managed under the hood and whether you can write code that works with the garbage collector rather than against it.
#24. What is the difference between concurrency and parallelism in Java?
These two terms are often used interchangeably but they describe fundamentally different things, and understanding the distinction matters when designing Java applications that need to handle multiple tasks efficiently.
Concurrency
Concurrency is about dealing with multiple tasks at once, but not necessarily running them at the same time. On a single-core CPU, a concurrent program rapidly switches between tasks, giving the appearance of simultaneous execution. The focus is on managing and coordinating multiple tasks efficiently rather than running them in parallel.
Concurrency is well suited to I/O-bound workloads, such as handling web requests or reading from a database, where tasks spend a lot of time waiting rather than actively using the CPU.
// Concurrent tasks managed by an ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2"));
executor.shutdown();Parallelism
Parallelism is about actually running multiple tasks at the same time, which requires multiple CPU cores. Rather than switching between tasks, a parallel program splits work across cores so that each piece runs simultaneously.
Parallelism is well suited to CPU-bound workloads, such as image processing, matrix calculations, or large data transformations, where the bottleneck is processing power rather than waiting on external resources.
// Parallel processing using a parallel stream
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.parallelStream()
.map(n -> n * 2)
.forEach(System.out::println);A helpful way to think about it: concurrency is about structure and coordination, while parallelism is about execution speed. A program can be concurrent without being parallel, and parallel without being particularly well structured for concurrency.
Why would an interviewer ask this?
This question separates candidates who have a surface-level understanding of multithreading from those who truly understand how Java handles multiple tasks. Interviewers ask this to see if you can identify the right approach for a given problem and understand the trade-offs involved.
#25. How does Java's memory model work?
When multiple threads run at the same time in Java, they all share the same heap memory. This creates a challenge: how do you ensure that when one thread updates a variable, other threads see the updated value rather than a stale cached copy? That's exactly the problem the Java Memory Model (JMM) was designed to solve.
Main memory and thread-local memory
Each thread in Java has its own working memory, which acts as a local cache for performance reasons. Threads read variables from main memory into their working memory, operate on them locally, and write updates back to main memory when needed.
The problem is that without proper synchronisation, there's no guarantee of when that write-back happens, which can leave other threads reading outdated values.
The happens-before relationship
The JMM defines a concept called the happens-before relationship, which guarantees that certain actions are visible and ordered correctly across threads.
For example, if thread A writes to a variable inside a synchronized block and thread B later reads that variable inside its own synchronized block on the same object, the JMM guarantees that thread B will see thread A's write.
How synchronisation helps
Java provides several tools to enforce the happens-before relationship and ensure thread safety.
synchronized blocks and methods ensure that only one thread can execute a section of code at a time, and that any changes made within that block are visible to other threads that subsequently acquire the same lock.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}volatile variables ensure that reads and writes go directly to main memory, making changes immediately visible to all threads without the overhead of full synchronisation.
AtomicInteger and similar classes in java.util.concurrent.atomic provide thread-safe operations on single variables without needing explicit synchronisation.
Why would an interviewer ask this?
The Java Memory Model is one of the more advanced concurrency topics and one that trips up even experienced developers. Interviewers ask this to see if you understand what's actually happening under the hood when multiple threads interact with shared data, and whether you can reason about thread safety beyond just knowing which keywords to use.
#26. What are Java's design patterns, and when would you use them?
Design patterns are reusable solutions to problems that come up repeatedly in software development. Rather than solving the same problem from scratch every time, patterns give you a proven approach that other developers will also recognise and understand. In Java, design patterns are grouped into three categories based on what problem they solve.
Creational patterns
Creational patterns deal with how objects are created.
The Singleton pattern ensures only one instance of a class exists throughout the application. A common use case is a database connection pool where you only ever want one shared instance.
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}The Factory Method pattern provides a way to create objects without specifying their exact type upfront, letting subclasses decide which object to create. This is useful when generating different types of objects based on input, such as different shapes in a graphics application.
The Builder pattern constructs complex objects step by step, which improves readability and avoids constructors with too many parameters. A good example is building an HTTP request object with optional headers, parameters, and a body.
Structural patterns
Structural patterns deal with how classes and objects are composed together.
The Adapter pattern bridges two incompatible interfaces so they can work together, such as adapting a legacy payment gateway to a modern e-commerce platform.
The Decorator pattern adds behaviour to an object dynamically without modifying its structure. For example, wrapping a data source with encryption:
class EncryptionDecorator implements DataSource {
private DataSource wrapped;
public EncryptionDecorator(DataSource source) {
this.wrapped = source;
}
public void writeData(String data) {
wrapped.writeData(encrypt(data));
}
public String readData() {
return decrypt(wrapped.readData());
}
private String encrypt(String data) { return "encrypted_" + data; }
private String decrypt(String data) { return data.replace("encrypted_", ""); }
}The Proxy pattern provides a stand-in for another object to control access to it, such as managing access to a remote service.
Behavioral patterns
Behavioral patterns deal with how objects communicate and share responsibilities.
The Observer pattern defines a one-to-many relationship where dependents are notified automatically when an object changes state. Event listeners in GUI applications are a classic example.
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}The Strategy pattern lets you swap algorithms at runtime. For example, switching between different sorting strategies without changing the code that uses them.
The Command pattern encapsulates a request as an object, which is useful for implementing undo functionality in applications like text editors.
Why would an interviewer ask this?
Design patterns come up constantly in senior and mid-level Java interviews. Interviewers ask this to see if you can recognise common problems and apply proven solutions, and whether you can communicate your design decisions clearly to other developers.
#27. How does Java handle multithreading using the ExecutorService?
Creating a new thread manually every time you need to run a task in the background works, but it doesn't scale well. Each thread consumes memory and system resources, and creating and destroying them repeatedly adds unnecessary overhead. The ExecutorService, part of Java's java.util.concurrent package, solves this by managing a pool of reusable threads for you.
Rather than creating a new thread for each task, you submit tasks to the executor, and it assigns them to available threads from the pool. If all threads are busy, the task waits in a queue until one becomes free.
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Task 1 executed by: " + Thread.currentThread().getName()));
executor.execute(() -> System.out.println("Task 2 executed by: " + Thread.currentThread().getName()));
executor.shutdown();Here, two tasks are submitted to a pool of two threads. The shutdown() call tells the executor to stop accepting new tasks and finish the ones already running before terminating cleanly.
The ExecutorService also gives you more control than raw threads. You can retrieve results from tasks using Future, which represents the result of an asynchronous computation that may not be ready yet.
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(() -> {
return 42;
});
System.out.println("Result: " + future.get()); // Blocks until the result is ready
executor.shutdown();Java provides several types of thread pools through the Executors factory class to suit different needs. newFixedThreadPool creates a pool with a set number of threads, newCachedThreadPool creates threads on demand and reuses idle ones, and newSingleThreadExecutor runs tasks sequentially on a single thread.
Why would an interviewer ask this?
The ExecutorService is the standard way to manage concurrency in Java applications and is used extensively in production systems.
Interviewers ask this to see if you understand thread pool management, can avoid common pitfalls like forgetting to shut down the executor, and know how to retrieve results from asynchronous tasks.
#28. What is the purpose of the CompletableFuture class?
Before CompletableFuture was introduced in Java 8, handling asynchronous tasks meant working with the Future interface, which had a significant limitation: the only way to get a result was to call get(), which blocked the current thread until the task finished. For applications that need to stay responsive, this was a real problem.
CompletableFuture solves this by letting you define what should happen when a task completes, rather than sitting and waiting for it. You can chain operations together, combine multiple tasks, and handle errors, all without blocking.
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(greeting -> greeting + ", World!")
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return null;
});Here, supplyAsync runs the initial task on a background thread. thenApply transforms the result when it's ready. thenAccept consumes the final result and exceptionally handles any errors that occurred along the way. None of these steps block the main thread.
You can also combine multiple asynchronous tasks. thenCombine lets you process the results of two tasks together once both complete, while allOf waits for a group of tasks to all finish before continuing.
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "World");
task1.thenCombine(task2, (a, b) -> a + ", " + b)
.thenAccept(System.out::println); // Output: Hello, WorldWhy would an interviewer ask this?
CompletableFuture is the standard way to write non-blocking asynchronous code in Java and is widely used in modern applications. Interviewers ask this to see if you understand asynchronous programming beyond just spawning threads, and whether you can compose tasks and handle failures cleanly.
#29. What are the key features introduced in Java 17 and Java 21?
Java 17 and Java 21 are both long-term support releases, meaning they receive extended maintenance and are the versions most companies standardise on. Between them, they introduced some of the most significant changes to the language in years, and interviewers increasingly expect candidates to be familiar with them.
Java 17
Sealed classes allow you to control exactly which classes can extend or implement a type. Rather than leaving a class open to any subclass, you explicitly list which subclasses are permitted, making your code easier to reason about and allowing the compiler to catch exhaustiveness issues.
sealed class Shape permits Circle, Square {}
final class Circle extends Shape {}
final class Square extends Shape {}Text blocks, standardised in Java 15 and widely adopted by Java 17, make it much easier to work with multi-line strings like JSON, HTML, or SQL without needing concatenation or escape characters.
String json = """
{
"name": "Alice",
"age": 30
}
""";Pattern matching for instanceof removes the need for explicit casting after a type check. Instead of checking the type and then casting separately, you can do both in one step.
// Before Java 16
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// With pattern matching
if (obj instanceof String s) {
System.out.println(s.length());
}Java 21
Virtual threads were one of the headline features of Java 21, making high-concurrency applications much easier to write and scale.
Pattern matching for switch extends the pattern matching concept to switch statements, letting you match on types and conditions in a much more concise way.
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "Null value";
default -> "Unknown type";
};
}Sequenced collections introduced a new set of interfaces that give collections a defined encounter order with consistent methods for accessing the first and last elements, something that was surprisingly inconsistent across different collection types before.
Record patterns allow you to deconstruct records directly in pattern matching, making it easier to work with complex data structures.
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}Why would an interviewer ask this?
Java 17 and 21 are the two LTS versions most companies are either already running or actively planning to migrate to. Interviewers ask about these features to see if you're keeping up with the language and can take advantage of modern Java rather than writing code the way it was done in Java 8.
#30. How does Java support modular programming?
As Java applications grow larger, managing dependencies and controlling what parts of your code are accessible to other parts becomes increasingly difficult. Java 9 addressed this with the module system, also known as Project Jigsaw, which gives you a structured way to organise code into self-contained units called modules.
What is a module?
A module is a group of related packages and resources that explicitly declares what it depends on and what it exposes to the outside world. This is defined in a module-info.java file at the root of the module.
module com.example.myapp {
requires java.sql;
exports com.example.myapp.services;
}In this example, the module declares that it needs java.sql to function, and that it makes the com.example.myapp.services package available to other modules. Any internal packages that aren't explicitly exported remain hidden, even if they're public classes.
Why this matters
Before the module system, Java used the classpath to manage dependencies, which had no concept of encapsulation at the package level. Any public class was accessible to any other code on the classpath, making it easy to accidentally depend on internal implementation details of a library.
The module system fixes this by enforcing clear boundaries between modules. It also lets you use the jlink tool to build custom runtime images that only include the modules your application actually needs, which significantly reduces the size of your deployable artifact.
Why would an interviewer ask this?
Modular programming is increasingly relevant as Java is used more in containerised and cloud-native environments where smaller deployment sizes matter. Interviewers ask this to see if you understand how Java manages dependencies and encapsulation at a larger scale than just classes and packages.
#31. What is the Optional class in Java, and how is it used?
One of the most common sources of bugs in Java is the NullPointerException. It happens when you try to call a method or access a field on an object that turns out to be null, and without careful null checking throughout your code it can be surprisingly easy to miss.
The Optional class, introduced in Java 8, provides a cleaner way to handle values that might or might not be present. So that rather than returning null from a method and hoping the caller remembers to check for it, you can return an Optional that explicitly communicates the possibility of absence.
Optional<String> nonEmpty = Optional.of("Hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(null); // Safe way to wrap a value that might be nullOnce you have an Optional, you can work with it in a safe and expressive way without ever needing an explicit null check.
Optional<String> optional = Optional.ofNullable("Hello, World!");
// Only runs if a value is present
optional.ifPresent(value -> System.out.println(value));
// Returns the value or a default if empty
String result = optional.orElse("Default Value");
// Transform the value if present
Optional<Integer> length = optional.map(String::length);The real benefit of Optional is that it makes the possibility of absence explicit in your API. When a method returns Optional<String> instead of String, callers know they need to handle the case where no value is present, rather than discovering it at runtime through a NullPointerException.
That said, Optional is not meant to be used everywhere. It works best as a return type for methods that might not have a result to return. It should not be used for fields, method parameters, or collections, where null checks or empty collections are more appropriate.
Why would an interviewer ask this?
Optional is widely used in modern Java code and reflects a broader shift toward more expressive, functional-style programming. Interviewers ask this to see if you understand not just how to use it, but when it's the right tool and when it isn't.
#32. How does the Stream API work in Java, and why is it useful?
Before the Stream API was introduced in Java 8, processing collections of data typically meant writing loops with conditional logic inside them. It worked, but it could get verbose quickly and the intent of the code wasn't always obvious at a glance. The Stream API introduced a more expressive, functional style of working with data that focuses on what you want to do rather than how to do it step by step.
A stream is a sequence of elements that you can process through a pipeline of operations. That pipeline has three stages.
First you create a stream from a source like a collection or array
Then you apply any number of intermediate operations that transform or filter the data
Finally you apply a terminal operation that produces a result or side effect and closes the stream
List<String> names = List.of("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.toList();
System.out.println(result); // Output: [ALICE]Here, filter keeps only names starting with "A", map converts them to uppercase, and toList collects the results. Each step reads clearly and the overall intent of the code is immediately obvious.
One of the most important characteristics of streams is lazy evaluation. Intermediate operations like filter and map don't actually do any work until a terminal operation is called. This means that if you chain several operations together, Java can optimise the execution and avoid unnecessary processing.
Streams also support aggregation and grouping through collectors, which open up powerful ways to summarise data.
Map<Boolean, List<String>> grouped = names.stream()
.collect(Collectors.partitioningBy(name -> name.length() > 3));
System.out.println(grouped);
// Output: {false=[Bob], true=[Alice, Charlie, David]}For large datasets, you can switch to a parallel stream with a single method call, which distributes the work across multiple CPU cores automatically.
This should be used carefully, though, since it introduces thread safety considerations and doesn't always improve performance for small collections.
names.parallelStream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);Why would an interviewer ask this?
The Stream API is one of the most heavily used features in modern Java and comes up in almost every Java role. Interviewers ask this to see if you can write clean, expressive data processing code and understand important concepts like lazy evaluation, terminal operations, and when parallel streams are and aren't appropriate.
#33. What is the difference between Comparable and Comparator in Java?
Both Comparable and Comparator are used to define how objects should be sorted in Java, but they differ in where that sorting logic lives and how flexible it is.
Comparable
Comparable is implemented directly on the class whose objects you want to sort. It requires you to override the compareTo() method, which defines the natural ordering of the object. This is the default order used when you call Collections.sort() or add objects to a TreeSet without specifying any additional instructions.
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
}
// Sorts by ID automatically
Collections.sort(studentList);Because the sorting logic is baked into the class itself, Comparable is best when there is one obvious natural order for an object, like sorting students by ID or products by price.
Comparator
Comparator is a separate object that defines a sorting order externally, without touching the class being sorted. This makes it much more flexible since you can define as many different comparators as you need for different sorting scenarios.
Comparator<Student> byName = (s1, s2) -> s1.getName().compareTo(s2.getName());
Comparator<Student> byId = Comparator.comparingInt(Student::getId);
Collections.sort(studentList, byName); // Sort by name
Collections.sort(studentList, byId); // Sort by IDYou can also chain comparators together when you need to sort by multiple criteria, such as sorting by name first and then by ID as a tiebreaker.
Comparator<Student> combined = Comparator.comparing(Student::getName)
.thenComparingInt(Student::getId);Why would an interviewer ask this?
Sorting is a fundamental operation that comes up constantly in real-world Java applications. Interviewers ask this to see if you understand the difference between a fixed natural order and flexible custom sorting, and whether you can apply the right approach depending on the situation.
How did you do?
So there you have it! 33 of the most common Java coding interview questions and answers that you might encounter.
What did you score? Did you nail all 33 questions? If so, it might be time to move from studying to actively interviewing!
Didn't get them all? Don't worry; I'm here to help!
If you find that you’re struggling with the questions in this guide, or want to build some more impressive projects for your portfolio, then check out my Java programming bootcamp:
Updated for 2026, you'll learn Java programming fundamentals all the way from complete beginner to advanced skills. Better still, you’ll be able to reinforce your learning with over 80 exercises and 18 quizzes.
This is the only course you need to go from complete programming beginner to being able to get hired as a Java Developer!
Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students, and working Java developers.
If you join or not, I just want to wish you the best of luck with your interview!
Best articles. Best resources. Only for ZTM subscribers.
If you enjoyed this post and want to get more like it in the future, subscribe below. By joining the ZTM community of over 100,000 developers you’ll receive Web Developer Monthly (the fastest growing monthly newsletter for developers) and other exclusive ZTM posts, opportunities and offers.
No spam ever, unsubscribe anytime






