Java interview questions

Core language, collections and concurrency, with every snippet compiled and run.

Short answer

A core Java interview works through four layers: object-oriented basics such as overriding against overloading, object identity and the equals and hashCode contract, the collections framework and when each implementation is the right one, and the beginnings of concurrency. Senior rounds add streams, the memory model and whichever framework the team uses, usually Spring Boot. Quote the contracts accurately and you have most of the marks.

Last reviewed October 13, 2026

How a Java interview round is usually built

Screening starts with definition questions, often in a form or over the phone: what is the difference between an interface and an abstract class, what does final do, why is String immutable. The next round turns those into short code reading. You are shown eight lines and asked what they print, which is a cheap and effective way to find out whether you understand references rather than syntax.

A technical round then goes deeper on whatever the team touches daily. Backend teams ask about collections, exceptions and the API layer. Teams with throughput problems ask about threads and the memory model. Test automation roles ask about Selenium bindings, JUnit and page objects. If the job description names Spring Boot, assume at least a third of the questions will be about it.

One more thing is being assessed throughout: whether you say what version you work with. Language features arrived in waves, and generics, lambdas, streams, var, records and switch expressions each belong to a different era. Saying “on Java 17 I would use a record here” tells an interviewer more than a list of definitions.

Object-oriented questions, answered precisely

Overloading and overriding sound similar and are not. Overloading means several methods in the same class share a name but take different parameters, and the compiler picks one at compile time from the static types. Overriding means a subclass replaces an inherited method with the same signature, and the decision happens at run time based on the actual object. That distinction is the reason an overridden method can be called through a parent-typed variable and still run the subclass version.

Interface against abstract class is the next pair. An interface describes a capability, a class can implement many of them, and since Java 8 an interface can carry default and static methods. An abstract class can hold state and constructors, and a class can extend only one. The practical answer: use an interface for a contract several unrelated classes can satisfy, use an abstract class when subclasses genuinely share fields and partial behaviour.

The question that trips people is whether Java passes objects by reference. It passes everything by value, and for an object the value is the reference. So a method can mutate the object it was handed, but assigning a new object to the parameter is invisible to the caller:

static void mutateAndRebind(StringBuilder sb) {
    sb.append(" two");              // the caller sees this
    sb = new StringBuilder("three"); // the caller does not
    sb.append(" four");
}

StringBuilder sb = new StringBuilder("one");
mutateAndRebind(sb);
System.out.println(sb);   // one two

Round off the topic with final, which prevents reassignment of a variable, overriding of a method and subclassing of a class, and with static, which attaches a member to the class rather than to an instance.

Strings, equality and the integer cache

String is immutable, so every operation that looks like an edit returns a new object. That is why building text in a loop with the plus operator creates garbage and why StringBuilder exists. Interviewers also expect you to know the string pool: identical literals are shared, so comparing them with == happens to work, while a string built with new is a separate object.

String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b);       // true  - the same pooled literal
System.out.println(a == c);       // false - a different object
System.out.println(a.equals(c));  // true  - the same characters

The rule to state afterwards is short: == compares references for objects, equals compares content, and you compare strings with equals.

The boxed-integer version of the same question is a favourite because the answer surprises people. The Integer documentation says valueOf will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range. Autoboxing goes through valueOf, so small values can share one object and larger ones usually do not.

Integer x = 127, y = 127;
Integer p = 128, q = 128;
System.out.println(x == y);        // true  - from the cache
System.out.println(p == q);        // false - two objects
System.out.println(p.equals(q));   // true

The takeaway is not the number 127. It is that you never compare boxed numbers with ==.

Collections and the equals and hashCode contract

Expect to justify a choice rather than recite a hierarchy. ArrayList gives fast access by index and cheap appends at the end, and shifts elements when you insert in the middle. LinkedList makes insertion and removal cheap once you are at the position, but reaching position n means walking there, so ArrayList wins for most ordinary code. HashSet stores unique elements with no order, LinkedHashSet keeps insertion order, TreeSet keeps them sorted. The same three-way choice repeats for maps.

The HashMap documentation answers three common questions in its own words. On performance: this implementation provides constant-time performance for the basic operations, get and put, assuming the hash function disperses the elements properly among the buckets. On ordering: this class makes no guarantees as to the order of the map. On threads: note that this implementation is not synchronized, and if multiple threads access a hash map concurrently and at least one of them modifies it structurally, it must be synchronized externally. For concurrent use the answer is ConcurrentHashMap rather than the legacy Hashtable.

That leads to the contract question. The Object documentation states it directly: if two objects are equal according to the equals method, then calling hashCode on each must produce the same integer result. The reverse is not required; unequal objects may share a hash code, and the documentation only notes that distinct results may improve hash-table performance. Break the contract and hash-based collections stop working, which you can watch happen:

static class Badge {
    final String code;
    Badge(String code) { this.code = code; }
}

record BadgeRecord(String code) {}

Set<Badge> plain = new HashSet<>();
plain.add(new Badge("A1"));
plain.add(new Badge("A1"));
System.out.println(plain.size());     // 2 - no equals, so two distinct objects

Set<BadgeRecord> records = new HashSet<>();
records.add(new BadgeRecord("A1"));
records.add(new BadgeRecord("A1"));
System.out.println(records.size());   // 1 - a record generates equals and hashCode

Mentioning records as the modern way to get both methods, and the IDE generator or Objects.hash for ordinary classes, is the answer interviewers hope for.

Streams, lambdas and Optional

Java 8 questions are now baseline. A stream is a pipeline over a source: intermediate operations such as filter and map are lazy and build up the pipeline, and a terminal operation such as collect, forEach or reduce makes it run. A stream is consumed once, which is the detail to volunteer.

List<String> names = List.of("ana", "ben", "cleo", "dev");

String joined = names.stream()
        .filter(n -> n.length() > 3)
        .map(String::toUpperCase)
        .collect(Collectors.joining(", "));
System.out.println(joined);          // CLEO

Map<Integer, List<String>> byLength = names.stream()
        .collect(Collectors.groupingBy(String::length));
System.out.println(byLength);        // {3=[ana, ben, dev], 4=[cleo]}

List.of returns an unmodifiable list, so calling add on it throws UnsupportedOperationException. That pairs well with the question about immutable collections, and it is the kind of thing worth trying once so you recognise the exception in a stack trace.

Optional is the other half of the topic. It exists so a missing value is visible in the type instead of arriving as null, and the right habit is to resolve it with orElse, orElseGet, map or ifPresent rather than calling get. If asked when not to use it, say: not as a field type and not as a method parameter; it was designed for return values.

A lambda is an implementation of a functional interface, an interface with one abstract method. Knowing the four names that cover most uses, Function, Predicate, Supplier and Consumer, plus the method reference syntax shown above, is enough for most interviews.

Threads, synchronized and volatile

Concurrency questions start gently: the difference between a process and a thread, between extending Thread and implementing Runnable (prefer Runnable, or hand the task to an executor), and what an ExecutorService gives you over creating threads by hand, which is a managed pool and a Future for each result.

Then comes the interesting part. The Java tutorial sets out the ground rules: reads and writes are atomic for reference variables and for most primitive variables, all types except long and double, and reads and writes are atomic for all variables declared volatile, including long and double. The same page explains the visibility half: any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable, so changes to a volatile variable are always visible to other threads.

The practical consequence is the question most people get half right. value++ is not one operation, it is a read, an add and a write, so two threads can read the same value and one update is lost. Volatile does not fix that, because it gives visibility and not atomicity. Try it with two threads incrementing one hundred thousand times each:

static class Counter {
    private int value = 0;
    void increment() { value++; }   // read, add, write: not atomic
    int get() { return value; }
}

Run that from two threads and the plain counter lands below 200,000 more often than not, while an AtomicInteger incremented in the same loop is exact every time. The fixes to name are a synchronized method or block, an AtomicInteger, or a lock from java.util.concurrent. Add that you would prefer immutable objects and the concurrent collections over sharing mutable state at all, and the follow-up about deadlock becomes easy: it needs two threads holding one lock each and waiting for the other, so you avoid it by taking locks in a consistent order.

Spring Boot, the JVM and how to answer under pressure

If the role is Spring based, the questions follow a pattern. What dependency injection buys you: components declare what they need, the container supplies it, and tests can supply something else. What a starter is: a dependency that pulls in a consistent set of libraries for one job, such as web or data access. What auto-configuration does: it configures beans based on what is on the classpath and on your properties, and backs off when you define your own. Expect questions about the controller, service and repository layers, about validation and exception handling in a REST API, and about the difference between a unit test and a test that starts the application context.

JVM questions stay shallow outside performance roles. Know that garbage collection reclaims unreachable objects, that you cannot force it and System.gc is only a hint, that the heap holds objects while each thread has its own stack, and that a memory leak in Java usually means a collection holding references nobody needs any more.

For the live part, read code out loud before you answer: name the types, say where the reference points, then predict the output. And when a question has a contract behind it, quote the contract. “Equal objects must have equal hash codes, the reverse is not required” lands better than a vague statement about buckets.

Practice it now

Answer out loud, as if the interviewer just asked. Then check yourself against the list.

“Explain why a mutable object used as a HashMap key can go missing, using an example, and say what you would do instead.”

Aim for 5 minutes
  • You stated the hashCode contract before explaining the failure.
  • Your example changes a field that equals and hashCode depend on, after insertion.
  • You explained that lookup goes to a different bucket, so get returns null while the entry is still there.
  • You proposed immutable keys, or a record, or keys built from fields that never change.
  • You mentioned how you would prove it in code, for example printing the map size after the mutation.

Frequently asked questions

What are the most common core Java interview questions?

Overloading against overriding, interface against abstract class, why String is immutable and how the pool works, == against equals, the equals and hashCode contract, choosing between ArrayList, LinkedList, HashMap and TreeMap, checked against unchecked exceptions, and the basics of threads and synchronized.

How deep do streams and lambdas questions go?

Usually to the level of writing a filter, map and collect pipeline, explaining that intermediate operations are lazy and that a stream is consumed once, and using groupingBy. Parallel streams come up mostly as a trap: say you would measure before using one.

Should I prepare Spring Boot even for a junior role?

If the job description names it, yes, at the level of dependency injection, starters, a REST controller and a repository. For a junior role, having built one small application end to end and being able to walk through its layers is usually enough.

What is the difference between checked and unchecked exceptions?

Checked exceptions extend Exception and the compiler forces you to catch or declare them, which suits recoverable problems such as missing files. Unchecked exceptions extend RuntimeException and usually signal a programming error, such as a null dereference or a bad argument.

How should I practise before a Java interview?

Write and run the small snippets rather than reading them: the string pool, the integer cache, a class without equals in a HashSet, two threads incrementing one counter. Seeing the surprising output once makes the explanation yours instead of remembered.

Sources

  1. Java SE 21 API documentation: Object, the hashCode general contract
  2. Java SE 21 API documentation: Integer.valueOf caching
  3. Java SE 21 API documentation: HashMap
  4. The Java Tutorials: Atomic Access