Skip to main content

Top Core Java Interview Questions & Answers

These questions cover fundamental Java concepts frequently asked in technical interviews, with answers expanded to senior-level depth.

1. Why is Java not a purely Object-Oriented language?

Java is not considered 100% object-oriented because it supports primitive data types like int, char, float, double, boolean, byte, short, and long. In a purely object-oriented language, everything should be an object.

Why do primitives exist at all?

Performance. Primitives live directly on the stack (or inline in object fields), whereas wrapper objects (Integer, Double, etc.) are heap-allocated. Accessing a primitive is a single memory read; accessing a wrapper requires pointer dereferencing, cache misses, and GC pressure. For tight loops processing millions of values, this difference is enormous.

The trade-off in modern Java

Java provides autoboxing/unboxing (since Java 5) to bridge the gap:

Integer boxed = 42; // autoboxing: int โ†’ Integer
int unboxed = boxed; // unboxing: Integer โ†’ int

Production gotcha: Autoboxing in hot loops silently creates millions of short-lived Integer objects, increasing GC pressure. Always use primitives for performance-critical code paths. Also beware of Integer cache: values between -128 and 127 are cached, so == works for those values but fails for larger ones.

Integer a = 127, b = 127;
System.out.println(a == b); // true (cached)

Integer c = 128, d = 128;
System.out.println(c == d); // false (different objects!)

2. What makes Java platform independent?

Java's independence comes from its bytecode. When you compile a Java program, the compiler converts the source code into bytecode (.class files) rather than machine-specific native code. This bytecode can run on any operating system (Windows, Linux, Mac) provided the system has a Java Virtual Machine (JVM) to interpret it.

Under the Hood

The key insight is that Java is platform-independent, but the JVM is not. Each OS has its own JVM implementation (HotSpot for Oracle, OpenJ9 for IBM, etc.) that translates the universal bytecode into platform-specific machine instructions. The bytecode format is standardized by the JVM Specification, ensuring consistent behavior across all compliant JVMs.

The Bytecode Format

A .class file starts with the magic number 0xCAFEBABE and contains:

  • Constant Pool โ€” all literals, class/method references
  • Access Flags โ€” public, final, abstract, etc.
  • Method bytecode โ€” stack-based instructions (e.g., iload, iadd, invokevirtual)

This standardized binary format is what makes "Write Once, Run Anywhere" possible.

3. Why is Java both interpreted and compiled?

Java uses a two-step execution process with an adaptive optimization strategy:

  1. Ahead-of-Time Compilation: The javac compiler converts source code (.java) into bytecode (.class).
  2. Interpretation + JIT: The JVM initially interprets bytecode line-by-line. As it runs, a profiler identifies hot spots โ€” methods or loops executed thousands of times.

JIT Tiered Compilation (Java 8+)

Modern HotSpot JVM uses tiered compilation with 5 levels:

LevelCompilerDescription
0InterpreterPure interpretation, collects profiling data
1C1 (Client)Simple optimizations, no profiling
2C1With invocation/backedge counters
3C1Full profiling (type checks, branch frequencies)
4C2 (Server)Aggressive optimizations (inlining, escape analysis, loop unrolling)

Key optimizations by C2:

  • Method inlining: Eliminates method call overhead by embedding the callee's code into the caller
  • Escape analysis: If an object doesn't escape a method, it can be stack-allocated (avoiding heap/GC)
  • Loop unrolling: Reduces loop overhead by duplicating the loop body
  • Dead code elimination: Removes code paths that are never executed

Production tip: Use -XX:+PrintCompilation to see which methods are JIT-compiled in your application.

4. Why are Strings immutable in Java?

Strings are immutable (cannot be changed once created) for several critical reasons:

  • String Pool Sharing: The JVM maintains a String Constant Pool where identical string literals share the same memory. If strings were mutable, changing one reference would corrupt all others pointing to the same object.
  • Security: Strings are used for sensitive parameters like file paths, network connections, class names, and database URLs. Immutability ensures these values cannot be altered after validation โ€” imagine a filename being changed between a security check and the actual file operation (a TOCTOU attack).
  • Hashcode Caching: The hashCode() of a String is computed once and cached in a private int hash field. Since the value never changes, this makes Strings extremely efficient as HashMap keys โ€” the hashcode is computed once and reused for every lookup.
  • Thread Safety: Immutable objects are inherently thread-safe. Multiple threads can read the same String without synchronization.

Under the Hood (Java 9+ Compact Strings)

Before Java 9, Strings were backed by char[] (2 bytes per character, UTF-16). Java 9 introduced Compact Strings (-XX:+CompactStrings, on by default):

  • Strings containing only Latin-1 characters use a byte[] with 1 byte per character (LATIN1 encoding)
  • Strings with non-Latin characters use byte[] with 2 bytes per character (UTF-16 encoding)
  • A coder field (byte) tracks which encoding is used

This reduced String memory footprint by ~40% in typical enterprise applications.

5. What is a Marker Interface?

A Marker Interface is an interface that does not contain any methods or fields. Examples include Serializable, Cloneable, and Remote. They serve as a "tag" to inform the JVM or a framework that the implementing class has a specific behavior or capability.

How does it work internally?

The JVM uses instanceof checks at runtime. For example, ObjectOutputStream.writeObject() checks:

if (!(obj instanceof Serializable)) {
throw new NotSerializableException(obj.getClass().getName());
}

Marker Interfaces vs. Annotations

Since Java 5, annotations have largely replaced marker interfaces for new designs:

FeatureMarker InterfaceAnnotation
Compile-time type checkingโœ… Can be used in method signaturesโŒ Cannot constrain method parameters
Scope controlโŒ Applies to entire classโœ… Can target methods, fields, parameters
MetadataโŒ No additional dataโœ… Can carry attributes
ExampleSerializable@Deprecated, @FunctionalInterface

When to use which: If you need to define a type that can be used in method signatures (e.g., accepting only Serializable objects), use a marker interface. For everything else, prefer annotations.

6. Can we override a static method?

No. If you define a static method with the same signature in a subclass, it is known as method hiding, not method overriding.

The Key Difference: Binding

  • Static methods โ†’ Static binding (compile-time). The compiler decides which method to call based on the reference type.
  • Instance methods โ†’ Dynamic binding (runtime). The JVM decides which method to call based on the actual object type via the virtual method table (vtable).
class Parent {
static void greet() { System.out.println("Parent"); }
void hello() { System.out.println("Parent"); }
}

class Child extends Parent {
static void greet() { System.out.println("Child"); } // HIDING
void hello() { System.out.println("Child"); } // OVERRIDING
}

Parent ref = new Child();
ref.greet(); // "Parent" โ€” static binding, resolved at compile time
ref.hello(); // "Child" โ€” dynamic binding via vtable at runtime

Interview follow-up: Static methods are not part of the vtable, so polymorphism doesn't apply to them. This is why @Override on a static method causes a compilation error.

7. What is the difference between final, finally, and finalize?

  • final: A keyword with three uses:

    • Variable: Makes a constant (primitive) or immutable reference (object). The object's fields can still be mutated unless they are also final.
    • Method: Prevents overriding in subclasses. The JIT compiler can aggressively inline final methods.
    • Class: Prevents inheritance (e.g., String, Integer).
  • finally: A block in try-catch-finally that is guaranteed to execute whether an exception is thrown or not. Used for resource cleanup (closing streams, releasing locks).

    • Exception: finally does NOT execute if System.exit() is called or if the JVM crashes.
    • Modern alternative: Use try-with-resources (Java 7+) for AutoCloseable resources instead of manual finally blocks.
  • finalize(): A protected method in Object that the GC calls before reclaiming an object. Deprecated since Java 9 and removed in Java 18.

    • Why deprecated: Unpredictable execution timing, performance overhead (objects with finalizers require two GC cycles), and risk of object resurrection.
    • Modern alternative: Use java.lang.ref.Cleaner (Java 9+) or try-with-resources.

8. How do you create an Immutable Class?

To create a custom immutable class, follow these five rules:

  1. Declare the class as final so it cannot be extended (a subclass could add mutable state).
  2. Make all fields private and final.
  3. Do not provide any "setter" methods.
  4. Initialize all fields through a constructor.
  5. If the class contains mutable objects (e.g., Date, List), perform defensive copies in the constructor and getter methods.
public final class Employee {
private final String name;
private final List<String> skills;

public Employee(String name, List<String> skills) {
this.name = name;
// Defensive copy โ€” don't store the caller's reference
this.skills = new ArrayList<>(skills);
}

public String getName() { return name; }

public List<String> getSkills() {
// Return a copy โ€” don't expose internal state
return Collections.unmodifiableList(skills);
}
}

Why defensive copies matter: Without them, the caller can mutate the internal list through their original reference, breaking immutability. This is a common interview follow-up question.

Modern alternative (Java 14+): Use Records for simple immutable data carriers:

public record Employee(String name, List<String> skills) {
public Employee { // Compact constructor for defensive copy
skills = List.copyOf(skills);
}
}

9. What is a Singleton Class and how is it created?

A Singleton class ensures that only one instance of the class is created within a single JVM.

Thread-Safe Implementations (ranked by recommendation)

1. Enum Singleton (Best Practice โ€” Joshua Bloch, Effective Java)

public enum DatabaseConnection {
INSTANCE;

public void connect() { /* ... */ }
}

Enums are inherently thread-safe, serialization-safe, and reflection-proof. This is the simplest and most robust approach.

2. Double-Checked Locking (DCL) with volatile

public class Singleton {
private static volatile Singleton instance; // volatile is CRITICAL

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) { // 1st check (no lock)
synchronized (Singleton.class) {
if (instance == null) { // 2nd check (with lock)
instance = new Singleton();
}
}
}
return instance;
}
}

Why volatile is essential: Without it, the JIT compiler may reorder instructions. Thread A could see a partially constructed object โ€” instance is non-null but its fields aren't initialized yet. volatile prevents this by establishing a happens-before relationship.

3. Bill Pugh Singleton (Initialization-on-Demand Holder)

public class Singleton {
private Singleton() {}

private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return Holder.INSTANCE;
}
}

This leverages the JVM's class-loading guarantee: the inner class Holder is loaded (and INSTANCE initialized) only when getInstance() is first called. Thread-safe without synchronized blocks.

10. Is Java Pass-by-Value or Pass-by-Reference?

Java is strictly 100% Pass-by-Value. There is no pass-by-reference in Java.

The Source of Confusion

When an object is passed into a method, what is passed is the value of the reference variable (a copy of the memory address pointing to the heap object), NOT the reference itself.

public class PassByValueDemo {
public static void modify(Point p) {
p.x = 100; // Mutates object on heap (visible caller side)
p = new Point(500, 500); // Reassigns local copy of pointer (NOT visible to caller!)
}

public static void main(String[] args) {
Point pt = new Point(10, 20);
modify(pt);
System.out.println(pt.x); // Outputs 100 (state mutated)
System.out.println(pt.y); // Outputs 20 (reassignment did not affect pt)
}
}

Stack Frame Execution Model

  • When modify(pt) is called, a new stack frame is pushed.
  • A copy of pt pointer is passed to parameter p.
  • Modifying p.x follows the pointer to the heap object and mutates its field.
  • Reassigning p = new Point(...) only changes the local pointer in modify() stack frame. When the method pops off the stack, pt in main() still points to the original object.

11. Comparable vs Comparator in Java

Both interfaces are used for ordering objects, but they serve different design purposes:

FeatureComparable<T>Comparator<T>
Packagejava.langjava.util
Methodint compareTo(T o)int compare(T o1, T o2)
PurposeDefines Natural Ordering for a classDefines Custom / Multiple Orderings
Class ModificationMust modify the original domain classNo modification needed (external comparator)
UsageCollections.sort(list) or Arrays.sort(arr)Collections.sort(list, comparator)

Modern Java 8+ Comparator Chaining

List<Employee> employees = getEmployees();

// Multi-field sorting using method references & lambda chaining
employees.sort(
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName)
);

12. Can we restrict the access visibility of an Overridden Method?

No. An overriding method in a subclass cannot decrease the visibility of the parent class method. It can only maintain or increase visibility.

Rules of Visibility in Overriding:

  • Parent protected โ†’ Child can be protected or public (Cannot be default package-private or private).
  • Parent public โ†’ Child MUST be public.

Why? (Liskov Substitution Principle)

If subclass allowed restricting visibility (e.g. parent public โ†’ child private), dynamic polymorphism would break runtime safety:

Parent p = new Child();
p.publicMethod(); // Compiler checks Parent (public OK), but at runtime calls Child (private FAIL!)

To preserve OOP polymorphism guarantees, Java enforces that overridden methods must be as accessible as the superclass method.

13. What is Variable Shadowing vs. Field Hiding vs. Method Overriding?

  • Variable Shadowing: A local variable inside a method has the same name as an instance variable of the class. The local variable shadows the instance variable within that scope (accessed via this.varName).
  • Field Hiding: A subclass declares a field with the exact same name as a field in its superclass. The subclass field hides the superclass field.
  • Method Overriding: A subclass provides a specific implementation for an instance method defined in the superclass.
class Parent {
String name = "Parent Field";
}

class Child extends Parent {
String name = "Child Field"; // Hiding superclass field

void printNames(String name) { // Shadowing instance variable with parameter
System.out.println(name); // Local parameter
System.out.println(this.name); // Child field ("Child Field")
System.out.println(super.name); // Parent field ("Parent Field")
}
}

14. Can Dynamic Polymorphism be achieved with Instance Variables (Data Members)?

No. In Java, only instance methods are polymorphic (dynamically bound). Fields (instance variables) are resolved at compile-time based on the declared reference type, not the actual runtime object type.

class Parent {
int value = 10;
}
class Child extends Parent {
int value = 20;
}

Parent ref = new Child();
System.out.println(ref.value); // Outputs 10 (Parent value), NOT 20!

Why? Method resolution uses the JVM vtable (Virtual Method Table) for dynamic dispatch. Fields are stored at fixed byte offsets in memory layout resolved at compile time.

15. Association vs. Aggregation vs. Composition

These three terms define HAS-A relationships between classes:

  1. Association: General binary relationship between two separate classes (e.g. Teacher and Student). Both objects have independent lifecycles.
  2. Aggregation (Weak HAS-A): Special form of association representing a whole-part relationship where parts can exist independently of the container.
    • Example: Department and Professor. If the Department is deleted, Professor objects continue to exist.
  3. Composition (Strong HAS-A): Strict whole-part relationship where the child object's lifecycle is completely owned and managed by the parent. If the parent is destroyed, the child objects are also destroyed.
    • Example: Car and Engine (or House and Room). A Room cannot exist without a House.
// Composition: Car owns Engine lifecycle
public class Car {
private final Engine engine;
public Car() {
this.engine = new Engine(); // Engine created & owned inside Car
}
}

16. What are Covariant Return Types in Java?

Introduced in Java 5, Covariant Return Type allows an overriding method in a subclass to return a subtype of the return type declared in the superclass method.

class Producer {
public Number produce() { return Integer.valueOf(10); }
}

class IntegerProducer extends Producer {
@Override
public Integer produce() { // Allowed because Integer IS-A Number
return Integer.valueOf(42);
}
}

Benefits: Eliminates explicit downcasting on client code calling the specialized subclass method directly.

17. How does Method Overloading handle Type Promotion?

When calling an overloaded method, Java compiler selects the best match using the following order of precedence:

  1. Exact primitive match
  2. Widening primitive conversion (byte โ†’ short โ†’ int โ†’ long โ†’ float โ†’ double)
  3. Autoboxing (int โ†’ Integer)
  4. Varargs (int...)
public class OverloadDemo {
static void show(long a) { System.out.println("Widened to long"); }
static void show(Integer a) { System.out.println("Autoboxed to Integer"); }
static void show(int... a) { System.out.println("Varargs"); }

public static void main(String[] args) {
int val = 10;
show(val); // Outputs "Widened to long" (Widening beats Autoboxing!)
}
}

18. Serializable vs Externalizable in Java

FeatureSerializableExternalizable
Interface TypeMarker Interface (0 methods)Standard Interface (writeExternal, readExternal)
ControlJVM automatic default serializationDeveloper full manual control
PerformanceSlower (uses reflection to inspect fields)Faster (direct manual streaming)
ConstructorNo special constructor requiredRequires public no-arg constructor
transient KeywordRespected (transient fields skipped)Ignored (writeExternal() controls serialization)

Security & Vulnerability Warning

Standard Java Serialization (ObjectInputStream.readObject()) is a major security vulnerability vector (Arbitrary Code Execution via gadget chains). Modern microservices prefer JSON (Jackson), Protocol Buffers, or Avro over native Java serialization.


๐Ÿ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%