Java Full Stack Developer Top 100 Interview Questions
100 Java full stack developer interview questions with structured answers, comparison tables, flow diagrams and runnable examples. Covers core Java, concurrency, backend architecture, REST APIs, SQL, persistence, HTML, CSS, JavaScript, security, testing, deployment and system design.
Questions 1-20 of 100
Q1.What are the main responsibilities of a Java full stack developer?
A Java full stack developer delivers features across the browser, backend, and persistence layers. The responsibility includes how those layers behave together under real user activity.
- Frontend: build accessible interfaces, manage interaction and state, call backend APIs, and handle loading, validation, and failure states clearly.
- Backend: implement business rules, request contracts, authentication and authorization, and reliable integration with databases and external services.
- Data: design appropriate schemas, queries, indexes, and transaction boundaries while preserving consistency under concurrent requests.
- Delivery: test user flows, review security and performance, deploy compatible changes, and use production evidence to diagnose problems.
Strong full stack ownership means tracing a requirement from user action to durable outcome and explaining the trade-offs at each boundary. It does not require every layer to use the same programming language.
Q2.What is the difference between the JVM, JRE, and JDK?
The JVM executes Java bytecode. A runtime environment supplies execution capabilities and libraries, while a development kit also provides tools for building and diagnosing Java applications.
JVM JRE JDK Executes bytecode Provides the runtime environment Builds and runs applications Class loading, execution and memory management The virtual machine plus runtime libraries The runtime plus development tools When to use which:
- Use a JDK for compilation, development, and diagnostic workflows that require tools beyond the capabilities included in a minimal runtime image.
- Deploy an appropriate runtime or application-specific runtime image when it contains the modules and capabilities the application actually requires.
- Check the compiler target, runtime version, modules, and native dependencies together instead of assuming all Java binaries run on every installed runtime.
Modern distribution and packaging choices vary, so JRE remains a useful conceptual distinction even when a separate JRE download is not provided.
Q3.How does Java source code become an executing application?
Java source is normally compiled into bytecode, which a compatible JVM loads and executes. Runtime optimization can compile frequently executed paths into machine code.
- Compile source while checking language rules and types, producing class files that target a supported bytecode version.
- Load required classes, verify and link their bytecode, and initialize classes when the language’s initialization rules require it.
- Execute methods using the runtime’s available execution mechanisms, collecting profiling information where adaptive optimization is supported.
- Compile or optimize suitable hot paths, while garbage collection and other runtime services manage execution resources.
Compilation success does not prove runtime compatibility: absent dependencies, unsupported class versions, configuration, and native code can still cause failures. Performance also depends on startup behavior and warm-up, not just steady-state execution.
flowchart TD A["Compile Java source"] B["Load verify and link classes"] C["Execute application methods"] D{"Optimization useful?"} E["Compile optimized machine code"] F["Continue application execution"] A --> B B --> C C --> D D -->|yes| E D -->|no| F E --> FQ4.What are the four main principles of object-oriented programming?
Object-oriented programming organizes behavior and state around objects with defined responsibilities. The four commonly discussed principles describe different ways to control complexity and support change.
- Encapsulation: protect internal state behind operations that preserve valid behavior, rather than exposing every field for unrestricted modification.
- Abstraction: describe a useful contract while hiding implementation details that callers do not need to understand or depend on.
- Inheritance: derive a more specialized type from an existing type when the subtype can honor the inherited contract and meaning.
- Polymorphism: let callers invoke a common contract while runtime objects provide the appropriate implementation, reducing conditional logic tied to concrete types.
These principles work together, but more classes or deeper inheritance do not automatically produce a better design. Prefer boundaries that make business rules, dependencies, and testing easier to understand.
Q5.When should you use an interface instead of an abstract class in Java?
An interface defines a contract that different classes can implement. An abstract class can share instance state, construction logic, and partial implementation within a class hierarchy.
Interface Abstract class No per-instance fields Can define instance fields No instance constructors Can define constructors A class can implement several A class can extend only one superclass When to use which:
- Use an interface for a role or capability that multiple unrelated implementations should provide, especially when consumers should depend on a small contract.
- Use an abstract class when related implementations genuinely share state or construction rules and the single superclass relationship is appropriate.
- Consider default interface methods for compatible shared behavior, but avoid using them to hide a poorly defined contract or conflicting responsibilities.
Interfaces can contain supported default, static, and private methods; they are not limited to abstract methods. Choose the abstraction based on ownership and substitutability rather than syntax alone.
Q6.How do method overloading and method overriding differ?
Overloading provides different parameter signatures under the same method name. Overriding supplies a subtype implementation for an inherited instance-method contract.
Overloading Overriding Chosen at compile time from argument types Chosen at runtime from the receiver type, for the selected signature Uses a different parameter list Uses a compatible inherited signature Offers related call variations Gives subtype-specific behavior When to use which:
- Use overloading when related operations should be convenient to call with different parameter lists, while avoiding ambiguous combinations involving NULL or numeric conversions.
- Use overriding when a subtype should provide specialized behavior while preserving the superclass or interface contract expected by callers.
- Use the override annotation to help the compiler detect a mismatched signature instead of accidentally creating an unrelated overload.
Return type alone cannot distinguish overloads. Static methods are hidden rather than dynamically overridden, and overriding cannot weaken accessibility or arbitrarily broaden checked exceptions.
Q7.When should you prefer composition over inheritance?
Composition assembles behavior by holding and delegating to collaborators. Inheritance defines a subtype relationship that exposes the parent’s contract and extension assumptions.
Composition Inheritance The class has or uses a collaborator The class is a substitutable subtype Behavior can be replaced independently Specializes a supported hierarchy When to use which:
- Use composition when behavior should vary independently, when several strategies may be combined, or when the relationship is mainly reuse rather than true substitutability.
- Use inheritance when the child really is a valid parent-type substitute and the parent was designed with stable extension points and invariants.
- Review whether inherited operations still make sense for the subtype; disabling parent behavior can signal that the hierarchy represents the wrong relationship.
Composition introduces delegation and explicit dependencies, which is often a useful cost for reduced coupling. It is a design preference, not a rule that every superclass must be eliminated.
Q8.What do the SOLID principles mean in practical Java design?
SOLID is a set of design principles for keeping responsibilities and dependencies manageable. Apply them to likely changes and real contracts rather than creating abstractions without a purpose.
- Single responsibility: give a module one coherent reason to change, such as separating business calculations from persistence and message formatting.
- Open–closed: support expected variations through stable extension points instead of repeatedly modifying unrelated, already-working code.
- Liskov substitution: ensure a subtype honors the behavior and expectations of the type it replaces, including meaningful preconditions and outcomes.
- Interface segregation: expose focused contracts so consumers do not depend on operations irrelevant to their needs.
- Dependency inversion: make high-level policy depend on suitable abstractions, with implementation details supplied through explicit dependencies.
An abstraction should reduce change impact or improve clarity; a new interface for every class is not automatically an improvement.
Q9.How do reference equality, equals, and hashCode work together in Java?
For object references,
==checks whether two references identify the same object.equalscan define logical value equality, andhashCodemust remain consistent with that equality contract.- Equality contract: a correct equals implementation is reflexive, symmetric, transitive, consistent while relevant state is unchanged, and false when compared with NULL.
- Hash contract: logically equal objects must have equal hash codes, but unequal objects may share a hash code because collisions are possible.
- Collections: hash-based collections use both hashing and equality, so overriding equals without a compatible hashCode can make lookups and duplicate detection incorrect.
- Mutable keys: avoid changing fields used in equality or hashing while an object is stored as a map key or set element.
Primitive
==compares primitive values. Reference identity is useful when object identity itself is the requirement, rather than as a replacement for value equality.Q10.How do String, StringBuilder, and StringBuffer differ?
String represents an immutable character sequence. StringBuilder and StringBuffer provide mutable construction buffers, with different synchronization behavior.
String StringBuilder StringBuffer Immutable Mutable Mutable, with synchronized methods For stable text values and interfaces For building text within one thread For specific shared-buffer requirements When to use which:
- Use String for ordinary values that callers should not modify through a shared reference, including keys, messages, and completed text results.
- Use StringBuilder when repeatedly assembling text inside a local operation, especially loops where intermediate values would otherwise be discarded.
- Use StringBuffer only when its synchronization model fits an actual requirement; multiple method calls do not automatically form one atomic higher-level operation.
String immutability does not make the variable holding a String reference immutable. Also, simple concatenation may be optimized by the compiler and runtime, so measure before replacing every plus expression.
Q11.What is the difference between final, finally, and finalize?
These similarly named features have unrelated purposes. final is a language modifier, finally is part of exception handling, and finalize is a legacy finalization method that should not manage application resources.
final finally finalize Restricts reassignment or inheritance Runs cleanup after a try block A legacy finalization hook A final reference can still point to mutable data Not guaranteed after abrupt process termination Unreliable and deprecated for removal When to use which:
- Use final to express stable references or prevent supported forms of extension, while remembering that referenced objects may still expose mutable state.
- Use finally for cleanup that is not better represented by try-with-resources, and avoid returning from it because that can hide a pending result or exception.
- Use explicit lifecycle management and try-with-resources for closeable resources rather than depending on when or whether finalization happens.
Garbage collection is concerned with memory reachability; deterministic release of connections, files, or locks requires deliberate program structure.
Q12.How do checked exceptions and unchecked exceptions differ in Java?
Checked exceptions must be caught or declared when the compiler requires it. Unchecked exceptions include RuntimeException subclasses and Error subclasses and do not have that compile-time handling requirement.
Checked exception Runtime exception Error Must be caught or declared No mandatory declaration No mandatory declaration An explicitly modeled failure condition Invalid usage or an unchecked application failure A serious runtime condition When to use which:
- Use a checked exception when the API intentionally requires callers to consider a recoverable condition and that requirement improves the contract.
- Use a suitable unchecked exception when callers cannot usefully recover at that abstraction or when a precondition or invariant has been violated.
- Catch exceptions at a boundary that can recover, translate, or report them meaningfully, preserving their cause rather than logging and ignoring every failure.
throw raises a particular exception object, while throws declares possible checked exceptions in a method signature. Do not catch all Error values as an ordinary recovery strategy.
Q13.How does try-with-resources prevent resource leaks?
Try-with-resources closes declared AutoCloseable resources when control leaves the block, including ordinary exceptional exits. It makes resource ownership and release visible in the language structure.
- Acquire resources whose lifecycle belongs to the operation, using declarations or supported existing effectively final variables.
- Perform the work inside the block so early returns and exceptions still pass through the generated cleanup logic.
- Close successfully initialized resources in reverse declaration order; if both work and cleanup fail, inspect suppressed exceptions alongside the primary failure.
- Catch or propagate the resulting exception at a boundary that can respond meaningfully, preserving useful diagnostic context.
Closing a pooled connection usually returns it to the pool rather than destroying the physical connection. Resource cleanup still depends on the resource’s close implementation and cannot guarantee execution after abrupt process termination.
flowchart TD A["Acquire owned resource"] B["Perform block work"] C["Close initialized resources"] D{"Failure present?"} E["Return successful result"] F["Propagate or handle failure"] A --> B B --> C C --> D D -->|no| E D -->|yes| FExample
This Java program shows that the resource closes before the failure reaches the catch block.
class Main { static final class Resource implements AutoCloseable { Resource() { System.out.println("open"); } public void close() { System.out.println("close"); } } public static void main(String[] args) { try (Resource resource = new Resource()) { System.out.println("work"); throw new IllegalStateException("demo"); } catch (IllegalStateException exception) { System.out.println("caught"); } } } // Expected output: // open // work // close // caughtQ14.Is Java pass-by-value or pass-by-reference?
Java always passes arguments by value. For an object argument, the copied value is a reference to the object, so caller and callee can initially refer to the same mutable instance.
- Primitive arguments: the method receives a copy of the primitive value, and assigning to its parameter does not change the caller’s variable.
- Object arguments: modifying the shared object through the copied reference can be visible to the caller if that object allows mutation.
- Reassignment: assigning a different object to the method parameter changes only that local parameter, leaving the caller’s reference variable unchanged.
- API design: distinguish a method that mutates an input object from one that returns a replacement, and avoid surprising hidden changes to shared state.
The ability to mutate a referenced object does not make the argument-passing mechanism pass-by-reference.
Example
The shared box is changed, but parameter reassignment and primitive reassignment do not replace the caller’s variables.
class Main { static final class Box { int value; Box(int value) { this.value = value; } } static void update(Box box) { box.value = 7; box = new Box(99); } static void updateNumber(int number) { number = 99; } public static void main(String[] args) { Box box = new Box(1); int number = 1; update(box); updateNumber(number); System.out.println(box.value); System.out.println(number); } } // Expected output: // 7 // 1Q15.How do Java generics, type erasure, and bounded wildcards work?
Generics let APIs express relationships between types and catch many mistakes at compile time. Type erasure implements most generic type distinctions without creating a separate runtime class for each argument.
- Type safety: prefer parameterized collections and methods to raw types, so invalid values are rejected where possible instead of producing distant cast failures.
- Erasure: runtime objects generally do not retain their full generic arguments as directly testable instance types; bounds, casts, and bridge methods support compiled behavior.
- Producer bound:
? extends Tallows reading values as T, but does not safely allow adding arbitrary T instances because the specific subtype is unknown. - Consumer bound:
? super Tpermits adding T instances, while reading generally yields Object unless additional information is available.
Choose wildcards around how the parameter is used. Generic metadata can exist in declarations even though ordinary instance checks cannot distinguish every parameterization.
Q16.When should you use a List, Set, or Map in Java?
List, Set, and Map express different collection contracts. Select the contract first, then choose an implementation based on ordering, performance, mutability, and concurrency requirements.
List Set Map An ordered sequence that allows duplicates Unique elements under its equality rules Keys associated with values For positional or repeated values For membership and uniqueness For lookup and keyed aggregation When to use which:
- Use a List when position or repeated occurrences are meaningful, and choose whether efficient indexing or another access pattern matters most.
- Use a Set when membership and duplicate prevention are central, checking whether insertion order or sorted order is part of the required result.
- Use a Map when each key identifies an associated value, defining behavior for repeated keys, NULL values, and concurrent updates explicitly.
Map is a separate abstraction from Collection. Performance and iteration order come from the implementation, so a contract alone does not imply constant-time lookup or a stable presentation order.
Q17.How do ArrayList and LinkedList differ in performance and usage?
ArrayList stores elements in a resizable array, while LinkedList stores linked nodes. The cost of finding a position matters as much as the cost of changing links at that position.
ArrayList LinkedList Constant-time indexed access Indexed access traverses the list in general Amortized constant-time append Constant-time append Inserting at a located position may shift elements Inserting at a located position relinks nodes A compact array of references Extra node and link overhead When to use which:
- Use ArrayList for common list workloads involving iteration, indexed access, and appending, unless measured requirements indicate another structure is better.
- Consider LinkedList when its deque behavior or changes at an already-positioned iterator actually fit the workload, while accounting for node overhead.
- For frequent operations at both ends, evaluate a suitable array-based deque as well rather than assuming a linked list is automatically optimal.
Calling an indexed insertion on LinkedList can still take linear time to find the position. Benchmark realistic access patterns and memory use, not just isolated method names.
Q18.How does HashMap store and retrieve entries?
HashMap uses a key’s hash to locate a bucket and equality checks to identify the matching key within that bucket. Correct key equality and stable hash behavior are essential for reliable lookup.
- Insertion and lookup: compute the bucket from the hash, compare candidate keys appropriately, and update or return the value for the logically matching key.
- Collisions: different keys can share a bucket; the implementation manages these collisions, including tree-based handling under suitable conditions in modern implementations.
- Resizing: growing beyond configured capacity thresholds can redistribute entries, so initial sizing matters for large known workloads but should be measured.
- Guarantees: ordinary HashMap is not a general-purpose concurrent map and does not promise stable iteration order; average lookup performance depends on appropriate hashing.
Changing a stored key’s equality-relevant state can make its entry difficult to retrieve even though the object remains present in the map.
Q19.How does ConcurrentHashMap differ from a synchronized map wrapper?
Both approaches can support shared map access, but they provide different concurrency and iteration behavior. Thread-safe individual operations do not automatically make an arbitrary multi-step workflow atomic.
ConcurrentHashMap Synchronized map wrapper Designed for concurrent operations Typically guarded by one wrapper monitor Iteration gives a weakly consistent view Iteration needs documented external synchronization Null keys and values not supported Null support depends on the wrapped map When to use which:
- Use ConcurrentHashMap for concurrent access when its atomic operations and weakly consistent traversal match the application’s requirements.
- Use a synchronized wrapper for simpler shared-state needs when its locking model is acceptable and all access follows the required synchronization discipline.
- Use compute, merge, conditional replacement, or explicit locking for compound updates, rather than assuming a separate contains check and put form one atomic operation.
Keep mapping functions short and avoid unintended side effects or recursive updates. A concurrent map protects its mapping operations, not arbitrary mutation of objects stored as values.
Q20.How do map, filter, flatMap, and reduce differ in a Java stream pipeline?
Stream operations describe transformations over a source without turning the stream into a general-purpose stored collection. Most intermediate operations are lazy until a terminal operation requests a result.
- filter: retain elements satisfying a predicate, preserving the values themselves rather than converting each accepted element to another type.
- map: transform each element into one result, such as converting an order into its identifier or a name into a normalized value.
- flatMap: transform each element into a stream and flatten those streams, which is useful when one parent contains several child values.
- reduce: combine elements using a suitable associative operation and valid identity when required; mutable aggregation often belongs in a collector instead.
Avoid modifying the source or shared mutable state during processing. A stream is normally consumed once, and parallel execution requires correct combination logic rather than simply adding a parallel flag.