The classic Java questions senior candidates still get — data types, dynamic arrays, platform independence, variable kinds, static main, throw vs throws, try without catch, preventing serialization, constructors vs methods, copy constructors, cloning, coercion, new vs reflective instantiation, abstraction vs encapsulation, default methods, static methods, extends vs implements, assert, toString, annotations, PATH vs classpath, switch on String, compile-time constants, string concatenation, static vs final, try-catch, the equals/hashCode contract and instanceof — answered with the depth and corrections expected at 8+ years.
Published September 25, 2026
At 8+ years, a basic question is an invitation to show depth. Answer the basic version in one sentence, then add the detail a junior wouldn't know:
Several common prep-sheet answers to these questions are wrong or outdated, and they're corrected here.
Short answer:
byte, short, int, long, float, double, char, boolean. They hold values directly, have fixed sizes (int = 32 bits, long = 64 bits, char = 16-bit UTF-16 code unit), can't be null, and have defaults only as fields (0, false, '\u0000').String, records and enums), interfaces, arrays, and type variables. Variables hold references, which can be null.Common trap: saying non-primitive types are "derived from primitives". They aren't. They're objects on the heap, accessed by reference. Each primitive has a wrapper class for use in generics and collections (autoboxing), and Project Valhalla (value classes) aims to narrow this gap.
Learn it in depth → Classes and Objects
Short answer: Arrays are fixed-size once created. ArrayList is the dynamic array:
Object[] that grows by about 1.5× (newCapacity = old + old >> 1) when full, copying the elements;Key points to cover:
new ArrayList<>(expected), or ensureCapacity, when you know the volume. For primitives, int[] or specialised libraries avoid the boxing overhead.Short answer: javac compiles source to platform-neutral bytecode (.class files). Each platform has its own JVM that loads, verifies, interprets and JIT-compiles that bytecode to native code. So the same JAR runs on any OS and CPU that has a compatible JVM: "write once, run anywhere". The JVM itself is platform-specific.
Key points to cover:
Short answer: Java has no global variables. It has:
main static?Short answer: The launcher has to call main before any object exists, so it's static: it's invoked on the class, without creating an instance.
Key points to cover:
void main() without static or public works: the launcher instantiates the class through its no-arg constructor, then calls it.java Hello.java, and multi-file programs since Java 22.throw and throws?Short answer:
throw is a statement that throws one exception instance now: throw new IllegalArgumentException("qty < 0").throws is part of a method or constructor declaration, listing the checked exceptions it may propagate (unchecked ones may be listed for documentation): void read() throws IOException, SQLException.Overriding methods may declare fewer or narrower checked exceptions, never broader ones.
try block need a catch block?Short answer: No. It needs catch and/or finally. And try-with-resources needs neither: the automatic close() acts as the cleanup. try { … } finally { … } is common when you want cleanup, but want exceptions to propagate.
Short answer:
transient (they're skipped, and get default values on deserialisation), or declare serialPersistentFields.Serializable. If a superclass does, block it explicitly:private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException(getClass().getName());
}
private void readObject(ObjectInputStream in) throws IOException {
throw new NotSerializableException(getClass().getName());
}
Common trap: the source says to "extend NotSerializableException". You throw it from writeObject/readObject. You don't extend it.
Key points to cover:
ObjectInputFilter, Java 9+) if you must accept serialised data.Short answer:
| Constructor | Method | |
|---|---|---|
| Name | Same as the class | Any name |
| Return type | None (not even void) | Required (void or a type) |
| Invoked by | new, this(...), super(...) | An explicit call |
| Inherited or overridable | No | Yes (unless private, static or final) |
Can be abstract, static, final or synchronized | No | Yes |
| Default provided | A no-arg one, if you declare none | No |
Short answer: A constructor that takes another instance of the same class, and initialises the new object from it: public Order(Order other) { ... }. Java doesn't generate one (unlike C++). It's the preferred alternative to clone(): explicit, type-safe, it works with final fields, and you decide what's deep-copied.
public Order(Order other) {
this.id = other.id;
this.lines = new ArrayList<>(other.lines); // defensive copy of mutable state
this.shippingAddress = other.shippingAddress; // an immutable value can be shared
}
Short answer: Object.clone() creates a field-by-field (shallow) copy. The class must implement the Cloneable marker interface, or clone() throws CloneNotSupportedException, and it typically overrides clone() as public, with a covariant return type. Deep copies must be written manually: clone or copy the mutable fields.
Key points to cover:
Cloneable broken:
final fields;copyOf), or immutable objects.Cloneable is a marker interface without a clone method, which is part of why it's awkward.Short answer: Type conversion.
int → long → float → double, char → int, and autoboxing and unboxing.(int) 3.99 gives 3 (truncation), and (byte) 200 gives -56.Key points to cover:
long → float/double beyond 2²⁴ or 2⁵³.null throws NullPointerException.byte + byte into int.new and newInstance()?Short answer:
new: the class is known at compile time. It's the fastest option, checked by the compiler, and can use any accessible constructor.clazz.getDeclaredConstructor().newInstance(). Class.newInstance() is deprecated since Java 9: it propagates checked exceptions from the constructor unchecked, and only supports no-arg constructors.Reflection has access checks, and is slower, though modern JVMs optimise it well (method handles). It also needs module opens for private members.
Short answer:
private fields, invariants enforced in methods). It's an implementation-level mechanism.Example: a PaymentGateway interface is an abstraction. Inside StripeGateway, the private API key and retry state, changed only through methods, are encapsulation. They reinforce each other: encapsulation keeps the abstraction honest.
default keyword for in interfaces?Short answer: Since Java 8, a default method gives an interface method a body, so interfaces can evolve without breaking existing implementations. That's how Collection.stream() and Iterable.forEach() were added. Implementing classes can override them. default is also used in switch (the fallback branch) and in annotation element declarations.
Key points to cover:
A.super.method().Short answer: A method that belongs to the class, not to an instance. It's called as ClassName.method(), it can only directly access static members, and it has no this or super. Static methods are bound at compile time: they're hidden, never overridden. They're used for utilities (Math.max), static factories (List.of, Optional.of) and main.
Key points to cover:
implements and extends?Short answer:
extends one class: single inheritance of state and implementation.implements any number of interfaces: multiple inheritance of type, and of default behaviour.extends other interfaces (it can extend several).Record/Enum), but can implement interfaces.assert statement for?Short answer: assert condition : message; checks an internal invariant during development. If it's false, it throws AssertionError. Assertions are disabled by default at runtime: enable them with -ea (or -ea:com.myapp...).
Key points to cover:
Objects.requireNonNull, or explicit checks that throw IllegalArgumentException.assert.toString() for?Short answer: It returns a human-readable representation of an object, used implicitly in string concatenation, in String.valueOf, in logging and in debuggers. Object's default is ClassName@hexHashCode. Override it for value-like classes. Records generate one automatically.
Key points to cover:
toString. They end up in logs.toStrings. Bidirectional JPA relations with Lombok @ToString can cause a StackOverflowError, or trigger lazy loading.Short answer: Metadata attached to code elements: classes, methods, fields, parameters, types and packages. They're declared with @interface, and processed:
@Override, @FunctionalInterface, @SuppressWarnings);spring-boot-configuration-processor);@Transactional, JPA's @Entity, Bean Validation).Key points to cover:
@Retention (SOURCE/CLASS/RUNTIME) decides visibility. Only RUNTIME annotations are readable by reflection.@Target restricts where an annotation can be used. @Inherited, @Repeatable and @Documented are the other meta-annotations.@RestController is @Controller + @ResponseBody.PATH and the classpath?Short answer:
PATH is an OS environment variable. It tells the shell where to find executables such as java and javac.-cp/--class-path, the CLASSPATH environment variable, or a JAR manifest's Class-Path.Key points to cover:
--module-path), for JPMS modules.CLASSPATH is an anti-pattern.String in a switch?Short answer: Yes, since Java 7. The compiler turns it into a switch on hashCode(), followed by equals() checks to resolve collisions. So it's efficient: effectively O(1) dispatch, plus one comparison. Matching is case-sensitive, and a null selector throws NullPointerException (unless you use Java 21's case null).
Key points to cover:
case "A", "B" -> …, with no fall-through, which can return a value.Common trap: saying string switches are "inefficient because of hashing". The hash is computed once (and cached in the String), which is cheaper than a chain of equals calls in if/else.
Short answer: A final variable of primitive or String type, initialised with a constant expression (for example static final int MAX = 100; or static final String PREFIX = "ord-" + "v1";). The compiler inlines its value wherever it's used, and it can be used in case labels and annotation values.
Common trap: because the value is copied into the calling classes' bytecode, changing a public static final constant in a library without recompiling its clients leaves them using the old value. For values that may change, use a method or a non-constant initialiser.
Short answer:
+: fine for simple expressions. Since Java 9, the compiler uses invokedynamic (StringConcatFactory), which is efficient.StringBuilder: for loops. + inside a loop creates a new string each iteration, which is O(n²).String.join(", ", list), Collectors.joining(", ", "[", "]"), and StringJoiner, for delimiters.String.format/formatted(): readable, but slower.concat(): a single string only, and it throws on null.Text blocks (""", Java 15) handle multi-line literals. String templates were withdrawn (they were a preview in Java 21–22).
static and final?Short answer: They're orthogonal:
static means belongs to the class: one copy, shared, with no instance needed.final means can't change:
static final together makes a class constant (static final Duration TIMEOUT = Duration.ofSeconds(5);).
Key points to cover:
final on a reference variable makes the reference constant, not the object. A final List can still be modified.try-catch block for?Short answer:
try wraps code that may throw.catch handles specific exception types, most specific first (otherwise it's a compile error for unreachable catches).finally, or try-with-resources, performs cleanup.At a senior level, the point is where to handle exceptions:
Exception or Throwable broadly.hashCode()/equals() contract.Short answer:
equals must be reflexive, symmetric, transitive, consistent, and return false for null.hashCode must be consistent while the fields used in equals don't change.Break rule 2, and HashMap/HashSet fail to find the objects. Mutate a key's fields after inserting it, and it's "lost" in its old bucket.
Key points to cover:
Objects.equals/Objects.hash.null IDs.Learn it in depth → equals() and hashCode()
instanceof operator do?Short answer: x instanceof T is true if x is non-null and its runtime type is T or a subtype. For null, it's always false, so it never throws.
Key points to cover:
if (shape instanceof Circle c) { area = Math.PI * c.r() * c.r(); }.if (obj instanceof Point(int x, int y)).instanceof chains suggest missing polymorphism, or a sealed hierarchy + pattern-matching switch, where the compiler checks that all cases are handled.Q: What are the default values of fields vs local variables?
A: Fields get defaults (0, 0.0, false, '\u0000', null). Local variables get none. Reading one before assigning it is a compile error ("might not have been initialized").
Q: Why prefer List.of(...) or Map.of(...) for constants?
A: They're immutable (UnsupportedOperationException on modification), null-hostile (they reject nulls), and compact. A static final constant built from Arrays.asList can still have its elements replaced.
Q: Can an interface have private methods? A: Yes, since Java 9, both private instance and private static methods. They share code between default and static methods, without exposing it.
Q: Is String a primitive?
A: No. It's a final, immutable class (backed by a byte[] with a coder flag, since Java 9's compact strings), with special language support: literals, + concatenation, and the string pool.