{"id":1158,"date":"2026-07-24T20:46:00","date_gmt":"2026-07-24T15:16:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1158"},"modified":"2026-07-21T22:32:44","modified_gmt":"2026-07-21T17:02:44","slug":"java-developer","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/java-developer","title":{"rendered":"Java Developer Interview Questions (2026): Core Java to Spring Boot"},"content":{"rendered":"<p>Oracle shipped Java 21 as a Long-Term Support release in September 2023, finalizing virtual threads and generational garbage collection. Almost three years later, a lot of java developer interview questions asked in India and the US are still written for Java 8. That gap is worth knowing before you walk in, since sounding fluent in outdated syntax doesn&#8217;t help much when someone asks how a virtual thread differs from a plain OS thread.<\/p>\n<p>Here&#8217;s an opinion plenty of senior engineers would push back on: the average Java interview overweights collections trivia (name the six methods on the List interface) and underweights the expensive stuff, like why a Spring Boot service&#8217;s connection pool exhausted at 2am in production. Companies still ask both, and this page covers what actually comes up across fresher and experienced loops. TCS and Infosys ask it for onboarding batches numbering in the thousands, and Goldman Sachs asks a version of the same HashMap question at the senior level with a lot less patience for a shaky answer.<\/p>\n<p>The questions below are split by topic instead of by company: core language and OOP, collections, exceptions, concurrency, JVM internals and garbage collection, the Java 8+ functional style, Spring and Spring Boot, and two coding problems you should be able to write without a compiler open.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">8<\/span><span class=\"iq-stat__label\">Topics<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">2-5<\/span><span class=\"iq-stat__label\">Rounds<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Easy-Medium<\/span><span class=\"iq-stat__label\">Coding level<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Fresher-Senior<\/span><span class=\"iq-stat__label\">Experience range<\/span><\/div><\/div>\n<h2>Core Java and OOP fundamentals<\/h2>\n<p>Every Java loop starts here, whether it&#8217;s a campus round at Wipro or a mid-level loop at a product company. Interviewers use these three to filter out candidates who&#8217;ve memorized syntax without understanding why the language behaves the way it does.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real difference between an abstract class and an interface in Java?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An abstract class can hold constructor logic, instance fields, and non-public methods, but a class can only extend one. An interface, since Java 8, can also carry default and static methods, but it still can&#8217;t hold instance state, and a class can implement as many interfaces as it wants. Use an abstract class when subclasses share real implementation and state, a Vehicle base class with an odometer field, for example. Use an interface when you&#8217;re defining a contract that unrelated classes need to honor, like Comparable or Serializable.<\/p>\n<p>The multiple-inheritance workaround, implementing several interfaces at once, is the main reason interfaces exist at all in a single-inheritance language. Interviewers usually follow up by asking what happens if two default methods from different interfaces conflict. The answer: the compiler forces you to override the method explicitly and pick one, or write your own.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">ArrayList or LinkedList, which do you actually reach for?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Collections<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ArrayList, almost every time, and I&#8217;d argue LinkedList is one of the most overused &#8220;textbook correct&#8221; answers in Java interviews. ArrayList is backed by a resizable array: O(1) random access, and appends are amortized O(1) since the array only needs to double occasionally.<\/p>\n<p>LinkedList gives O(1) insertion and removal, but only at a node you already hold a reference to. Getting to that node in the middle of the list is still O(n), which erases the advantage for most real use cases, and LinkedList has worse cache locality since its nodes scatter across the heap instead of sitting contiguously. The one place LinkedList earns its keep is as a Deque implementation, and even there, ArrayDeque usually wins because it skips the per-node object overhead entirely.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Checked versus unchecked exceptions: why do some senior engineers avoid checked exceptions entirely?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exceptions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Checked exceptions (anything extending Exception except RuntimeException, like IOException or SQLException) must be declared or caught at compile time. Unchecked exceptions, extending RuntimeException, aren&#8217;t enforced by the compiler at all.<\/p>\n<p>The pushback on checked exceptions is real and comes from good engineers, not just laziness. They don&#8217;t compose with the Streams API or lambda expressions, since none of the standard functional interfaces (Function, Consumer, Supplier) declare a checked exception, so you end up wrapping everything in try-catch or a custom unchecked wrapper just to satisfy the compiler. Effective Java author Joshua Bloch has argued for using checked exceptions sparingly for exactly this reason. My take: use them for recoverable conditions a caller genuinely needs to handle differently, and use unchecked for programming errors nobody should be catching anyway.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the practical difference between the heap and the stack in a running JVM?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">JVM<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Each thread gets its own stack: a LIFO structure of frames, one per method call, holding local variables and partial results. Recurse too deep and you get a StackOverflowError, not an OutOfMemoryError.<\/p>\n<p>The heap is shared across every thread and holds every object created with new. It&#8217;s divided into generations, Young (an Eden space plus two Survivor spaces) and Old, and running out of room there throws OutOfMemoryError instead. Since Java 8 replaced PermGen with Metaspace, class metadata itself lives off-heap in native memory that grows automatically, which is why &#8220;PermGen space&#8221; errors mostly vanished from modern stack traces.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What makes an interface a &#039;functional interface,&#039; and how does that relate to lambda expressions?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Java 8+<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A functional interface declares exactly one abstract method (default and static methods don&#8217;t count against that limit). That single-abstract-method shape is what lets the compiler treat a lambda expression or method reference as an instance of that interface, since there&#8217;s only one method signature it could possibly implement.<\/p>\n<p>java.util.function ships the ones you&#8217;ll actually use: Function&lt;T,R&gt;, Predicate&lt;T&gt;, Supplier&lt;T&gt;, Consumer&lt;T&gt;, and BiFunction&lt;T,U,R&gt;. The @FunctionalInterface annotation is optional but worth adding to your own interfaces, since it makes the compiler reject an accidental second abstract method instead of failing quietly much later.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the three ways to inject a dependency in Spring, and which one should you actually use?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Constructor injection, setter injection, and field injection (the @Autowired-on-a-field pattern most tutorials teach first). Constructor injection is the one worth defaulting to: dependencies become final and immutable, the class is trivially testable without a Spring context at all (just call the constructor), and a missing dependency fails at application startup instead of surfacing as a null pointer three requests into production.<\/p>\n<p>Field injection hides the dependency list, makes the class hard to instantiate without reflection or a container, and is the pattern the Spring team itself now recommends against in its own documentation.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Reverse a singly linked list, iteratively.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Keep three pointers: previous, current, and next. Walk the list once, redirecting each node&#8217;s next pointer backward before moving all three pointers forward. No recursion, no extra data structure, O(n) time and O(1) space.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nclass ListNode {\n\n    int val;\n\n    ListNode next;\n\n    ListNode(int val) { this.val = val; }\n\n}\npublic ListNode reverseList(ListNode head) {\n\n    ListNode prev = null;\n\n    ListNode curr = head;\n    while (curr != null) {\n\n        ListNode next = curr.next;\n\n        curr.next = prev;\n\n        prev = curr;\n\n        curr = next;\n\n    }\n\n    return prev;\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Is Java still worth learning for a developer job in 2026?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Yes, and the argument against it is weaker than it sounds online. Java didn&#8217;t top <a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"noopener noreferrer\">Stack Overflow&#8217;s 2025 Developer Survey<\/a> technology rankings the way JavaScript, HTML\/CSS, and Python did, but it remains one of the most widely used languages in that survey, and it&#8217;s still the default choice for enterprise backends across banking, insurance, and India&#8217;s IT-services sector. The demand isn&#8217;t hype-driven, it&#8217;s install-base-driven: an enormous amount of existing Java infrastructure isn&#8217;t going anywhere, and someone has to maintain it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Do I need to know Spring Boot to get a Java developer job in India?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>For most product-based and mid-size company roles, yes, practically speaking. Spring Boot is the default framework for new Java backend work, and the Spring section above covers the questions that actually get asked about it. For large IT-services companies hiring fresh graduates into legacy maintenance projects, Spring Boot comes up less at the fresher stage and more once you&#8217;re staffed onto a specific project that uses it, though knowing it going in is still a clear advantage in the interview itself.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real difference between == and .equals() when comparing two objects?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Object Equality<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>== compares references for objects, it asks whether two variables point at the same spot in memory, not whether the objects hold the same data. .equals() is a method call, and what it does depends entirely on whether the class overrode it, Object&#8217;s default .equals() just falls back to ==, so if a class never overrides it, the two behave identically.<\/p>\n<p>A gotcha that catches people off guard: arrays never override equals(), so two arrays with identical contents will fail both == and .equals(), you need Arrays.equals() (or Arrays.deepEquals() for nested arrays) to compare contents. It&#8217;s a common bug when someone assumes array equality &#8220;just works&#8221; like a List&#8217;s does.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between final, finally, and finalize in Java?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Keyword Basics<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>final is a modifier. On a variable it means the reference can&#8217;t be reassigned after initialization, though the object it points to can still change internally, a final List is still fully mutable, you just can&#8217;t point that variable at a different List later. On a method it blocks overriding, on a class it blocks subclassing.<\/p>\n<p>finally is a block attached to try\/catch that always runs, whether the try succeeded, threw, or even returned, and it&#8217;s the standard place to close resources. finalize() was a method the garbage collector called on an object before reclaiming it, meant as a last chance for cleanup, but it was unreliable, there&#8217;s no guarantee it ever runs, it can resurrect an object by re-adding a reference to it, and it slows down collection. It was deprecated in Java 9 and is on its way out entirely, replaced by try-with-resources and Cleaner.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between throw and throws?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exception Syntax<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>throw is a statement that actually raises an exception at a specific line, you use it with one exception instance: throw new IllegalArgumentException(&#8220;bad input&#8221;). throws is part of a method&#8217;s signature, it declares which checked exceptions the method might let propagate to its caller, it&#8217;s a compile-time contract, not an action.<\/p>\n<p>A method&#8217;s throws clause can list several exception types separated by commas, but a single throw statement only ever throws one object, though that object can carry another exception as its cause through constructor chaining, which is how you preserve the original stack trace when you wrap and rethrow.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">String, StringBuilder, or StringBuffer, when do you actually reach for each one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">String Types<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>String is immutable, every concatenation creates a brand new String object under the hood, so building one up with += inside a loop copies everything that came before it on every iteration, which is O(n squared) for n appends. StringBuilder is mutable, backed by a resizable char array, and appending to it is close to O(1) amortized, which is why the compiler itself rewrites simple string concatenation chains into StringBuilder calls behind the scenes.<\/p>\n<p>StringBuffer does the exact same job as StringBuilder except every method is synchronized, so it&#8217;s safe if multiple threads mutate the same buffer, but that locking is wasted overhead in the far more common single-threaded case. StringBuilder is the default choice today, StringBuffer mostly shows up in code written before Java 5.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between the JVM, the JRE, and the JDK?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Java Platform<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The JVM is the actual runtime that executes bytecode, it handles class loading, bytecode verification, JIT compilation, and garbage collection, and it&#8217;s the piece that makes Java portable, since every platform ships its own JVM but the compiled bytecode stays the same everywhere. The JRE is the JVM plus the core class libraries (java.lang, java.util, and so on) needed to run a compiled program, but it has no compiler in it.<\/p>\n<p>The JDK is the JRE plus the development tools, javac to compile source into bytecode, jar, javadoc, jshell, and a debugger. If you&#8217;re only running a compiled .jar you technically only need a JRE, but if you&#8217;re compiling code you need the full JDK, and most people just install the JDK now since standalone JRE-only installers have become rare.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between method overloading and method overriding?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP Basics<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Overloading is having multiple methods with the same name but different parameter lists in the same class, and which one gets called is decided at compile time based on the argument types you pass, that&#8217;s static binding. Overriding is a subclass providing its own implementation of a method with the exact same signature as its superclass, and which version actually runs is decided at runtime based on the object&#8217;s real type, that&#8217;s dynamic binding, and it&#8217;s the mechanism that makes polymorphism work.<\/p>\n<p>A detail that trips people up: changing only the return type doesn&#8217;t create an overload, the compiler needs different parameters to distinguish two methods with the same name, and when overriding, the return type has to be the same or a covariant subtype of the parent&#8217;s, otherwise it&#8217;s just a compile error, not a new overload.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do you need to override hashCode() whenever you override equals()?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Core Java<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Because HashMap, HashSet, and every hash-based collection use hashCode() to pick a bucket before it ever calls equals() to confirm a match. If two objects are equal by your equals() logic but return different hash codes, the map will happily store both in different buckets, and a lookup for the second one silently returns null even though a &#8220;duplicate&#8221; already exists somewhere else in the table.<\/p>\n<p>The contract from the Object class is specific: equal objects must produce equal hash codes, though the reverse isn&#8217;t required. Most interviewers ask a follow-up: what breaks if you only override equals()? The honest answer is that everything compiles fine, and the bug only shows up at runtime, inside a Set that quietly holds duplicates nobody notices until a report comes back wrong.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain why Java strings are immutable, and what the string pool has to do with it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Strings<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A String object can&#8217;t change after creation. Every &#8220;modification&#8221;, concatenation, substring, replace, actually returns a new object. That immutability is what makes the string pool possible: since a literal can never change out from under another reference, the JVM can safely reuse one copy in a shared pool (part of heap memory since Java 7) instead of allocating a fresh object for every identical literal.<\/p>\n<p>This is also why == and .equals() diverge for strings. new String(&#8220;java&#8221;) == &#8220;java&#8221; returns false, because new explicitly bypasses the pool and allocates a separate object, while &#8220;java&#8221; == &#8220;java&#8221; (both literals) returns true because they point at the same pooled instance. Interviewers use this to check whether you understand reference equality versus value equality, not just whether you memorized the trick.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a ConcurrentModificationException, and how do fail-fast iterators differ from fail-safe ones?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Collections<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ArrayList and HashMap track a modCount field that increments on every structural change. Their iterators capture that count when created and check it on every next() call. If something else, another thread, or your own loop body, modifies the collection outside the iterator, next() throws ConcurrentModificationException. It&#8217;s a best-effort check, not a guarantee, so don&#8217;t rely on it for real thread safety.<\/p>\n<p>Fail-safe collections like CopyOnWriteArrayList or ConcurrentHashMap sidestep the exception entirely. CopyOnWriteArrayList&#8217;s iterator works off a snapshot array taken at creation time, so it never throws, but it also won&#8217;t see updates made after that snapshot. The fix for the common case, removing items while iterating a plain ArrayList, is simply to call the iterator&#8217;s own remove() method instead of the list&#8217;s.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">If a finally block has a return statement, what happens to a value or exception from the try block?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exceptions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The finally block always runs, even after a return or a thrown exception in the try block, and if finally itself contains a return statement, it wins outright. It silently discards whatever the try block was about to return and swallows any exception that was in flight.<\/p>\n<p>This gotcha shows up almost verbatim across interview loops because it tests whether you actually understand control flow instead of assuming finally is &#8220;just cleanup code.&#8221; The safe pattern is to never put a return, break, or continue inside finally. Try-with-resources, since Java 7, handles the common cleanup case more safely by auto-closing any AutoCloseable resource in reverse declaration order, without needing an explicit finally block at all.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">volatile versus synchronized: what does each one actually guarantee?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>volatile guarantees visibility only: a write to a volatile field is immediately visible to every thread that reads it afterward, because the JVM won&#8217;t let that value sit cached in a CPU register or get reordered across the write. It guarantees nothing about atomicity, count++ on a volatile int is still a read-modify-write with a race condition, volatile just makes the race visible sooner.<\/p>\n<p>synchronized guarantees both visibility and mutual exclusion. Only one thread can hold the monitor at a time, so compound operations become atomic, at the cost of contention and potential context-switch overhead. The short version interviewers want: reach for volatile on simple status flags and one-way signals, reach for synchronized or an AtomicInteger when the operation is actually a compound read-modify-write.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you implement a thread-safe Singleton without paying a synchronization cost on every call?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The naive approach, marking the whole getInstance() method synchronized, works but forces every caller through a lock forever, even though the lock is only needed once. The initialization-on-demand holder idiom avoids that entirely by relying on the JVM&#8217;s own guarantee that a class isn&#8217;t initialized until it&#8217;s first referenced, and that class initialization is already thread-safe.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\npublic class ConfigLoader {\n\n    private ConfigLoader() {}\n    private static class Holder {\n\n        static final ConfigLoader INSTANCE = new ConfigLoader();\n\n    }\n    public static ConfigLoader getInstance() {\n\n        return Holder.INSTANCE;\n\n    }\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>Double-checked locking with a volatile field is the other accepted answer, and older codebases use it a lot, but the holder idiom is shorter and doesn&#8217;t depend on getting the volatile declaration exactly right.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do most JVM garbage collectors split the heap into young and old generations?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">GC<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The weak generational hypothesis: empirically, most objects die young, and the ones that survive tend to live a long time. Collectors optimize for that shape instead of scanning the whole heap every cycle.<\/p>\n<p>Minor GCs run frequently against just the small Young generation using a copying collector: live objects get copied from Eden into a Survivor space, and objects that survive enough cycles (tracked by an age counter, the tenuring threshold) get promoted into Old. Old-generation collections happen far less often, since most of what lives there is genuinely long-lived, but they&#8217;re more expensive when they do run, typically a mark-sweep-compact pass across a much bigger region. <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/21\/gctuning\/z-garbage-collector.html\" target=\"_blank\" rel=\"noopener noreferrer\">Oracle&#8217;s HotSpot GC Tuning Guide<\/a> walks through exactly how this plays out for both G1 and ZGC, worth reading once instead of memorizing secondhand summaries.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between map() and flatMap() in the Streams API?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Java 8+<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>map() applies a function to each element and returns one result per element, so if that function itself returns a Stream, you end up with a Stream of Streams, nested one level deeper than you wanted. flatMap() applies the function and then flattens the result into a single stream, which is what you need when each input maps to zero, one, or many outputs.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nList&lt;String&gt; sentences = List.of(\u201cjava is verbose\u201d, \u201cstreams help a bit\u201d);\nList&lt;String&gt; words = sentences.stream()\n\n    .flatMap(s -&gt; Arrays.stream(s.split(\u201d \u201c)))\n\n    .collect(Collectors.toList());\n\n\/\/ [java, is, verbose, streams, help, a, bit]\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When should you use Optional, and what&#039;s the misuse interviewers usually probe for?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Java 8+<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Optional exists to signal, through the return type, that a method might not have a value to give back, so callers are forced to handle that case instead of silently risking a NullPointerException three calls later.<\/p>\n<p>The misuse that comes up constantly: using Optional as a method parameter or a field type. It doesn&#8217;t implement Serializable, it adds an allocation for no real benefit over a plain null check in those positions, and it just moves the same problem one layer over. Calling .get() without checking isPresent() first is the other tell, since that&#8217;s functionally identical to the null check you were trying to avoid, just wrapped in extra ceremony. My honest opinion: Optional as a parameter type is close to always a mistake, and I don&#8217;t think there&#8217;s a defensible case for it in a public API.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">@Component, @Service, and @Repository all register a Spring bean the same way. So why do all three exist?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>They&#8217;re all meta-annotated with @Component, so Spring&#8217;s component scan treats them identically for bean registration. The difference is what else rides along. @Repository adds automatic exception translation: Spring wraps persistence-specific exceptions (a raw SQLException, for instance) into its own unchecked DataAccessException hierarchy, so the service layer doesn&#8217;t need to know which database driver threw what.<\/p>\n<p>@Service and @Controller carry no extra framework behavior by default. They&#8217;re semantic markers mainly for readability and for AOP pointcuts, transaction advice, for example, is commonly configured to target @Service-annotated classes by convention, not because the framework requires it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Check whether a string of brackets is balanced, using Java.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Push opening brackets onto a stack. On a closing bracket, pop and confirm it matches the expected opener; if the stack is empty or the popped value doesn&#8217;t match, the string isn&#8217;t balanced. At the end, the string is only balanced if the stack is also empty, a common miss is forgetting that last check and letting an input like &#8220;(()&#8221; pass as valid.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\npublic boolean isBalanced(String s) {\n\n    Deque&lt;Character&gt; stack = new ArrayDeque&lt;&gt;();\n\n    Map&lt;Character, Character&gt; pairs = Map.of(\u2018)\u2019, \u2018(\u2018, \u2018]\u2019, \u2018[\u2019, \u2018}\u2019, \u2018{\u2018);\n    for (char c : s.toCharArray()) {\n\n        if (pairs.containsValue(c)) {\n\n            stack.push(c);\n\n        } else if (pairs.containsKey(c)) {\n\n            if (stack.isEmpty() || stack.pop() != pairs.get(c)) return false;\n\n        }\n\n    }\n\n    return stack.isEmpty();\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Should I prepare with Java 8, Java 17, or Java 21 syntax?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Know Java 8&#8217;s stream and lambda syntax cold, since that&#8217;s still the baseline almost every interviewer assumes. Then layer on records, pattern matching for switch, and virtual threads as the newer material, since Spring Boot 3 requires Java 17 as a minimum and a growing number of interviewers, especially at product companies, are starting to ask about virtual threads specifically. You don&#8217;t need Java 21 mastery for a fresher role. You do need to not look blank when a senior interviewer brings it up.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why can comparing two boxed Integer objects with == give inconsistent results depending on their value?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Integer Caching<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Autoboxing an int literal into an Integer doesn&#8217;t call new Integer(), it calls Integer.valueOf(), and valueOf() returns cached, shared instances for values from -128 to 127 (the IntegerCache class, whose upper bound can be raised with -XX:AutoBoxCacheMax). Two Integer variables boxed from the same value inside that range end up pointing at the same cached object, so == returns true, but the same code with a value outside that range allocates two distinct objects, and == returns false.<\/p>\n<p>This is exactly why production code should never use == on boxed types, use .equals() or unbox to primitives before comparing. The sharper trap is unboxing a null Integer, which throws a silent NullPointerException the moment it&#8217;s used in ==, an if condition, or arithmetic, with no .intValue() call visible anywhere in the code to hint at what happened.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nInteger a = 100;\n\nInteger b = 100;\n\nSystem.out.println(a == b); \/\/ true, both reference the cached Integer\nInteger x = 200;\n\nInteger y = 200;\n\nSystem.out.println(x == y); \/\/ false, both are new objects outside the cache range\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">HashMap, Hashtable, and ConcurrentHashMap all store key-value pairs, so what actually separates them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Map Implementations<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Hashtable is a leftover from Java 1.0, every method is synchronized on the entire table, it rejects null keys and null values outright, and today you&#8217;d only find it in an old codebase, nobody reaches for it in new code. HashMap allows one null key and any number of null values, isn&#8217;t synchronized at all, which makes it fast single-threaded but genuinely unsafe under concurrent writes, in Java 7 concurrent puts during a resize could even corrupt the bucket structure into an infinite loop.<\/p>\n<p>ConcurrentHashMap is the one you actually use in concurrent code. It doesn&#8217;t lock the whole map, in Java 8 and later it uses CAS operations for the common case of inserting into an empty bin and only synchronizes on the specific bin&#8217;s head node when there&#8217;s a collision to resolve, so lock granularity is per-bin, not per-table or per-segment like the older Java 7 design. It also refuses null keys and values, deliberately, because a null return value from get() would be ambiguous with a concurrent map, you couldn&#8217;t tell if a key is absent or another thread just mapped it to null.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between Comparable and Comparator, and when would you reach for each?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sorting Ordering<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Comparable is implemented by the class itself through compareTo(), it defines one natural ordering, and a class can only have one. Comparator is a separate object that defines an ordering externally through compare(a, b), and you can write as many of them as you need for the same class without ever touching its definition.<\/p>\n<p>Use Comparable when there&#8217;s one obvious sort order for the domain object, sorting Money by amount for example. Use Comparator when the ordering is a runtime decision, sorting employees by last name on one screen and by hire date on another. Java 8&#8217;s Comparator.comparing(Employee::getLastName).thenComparing(Employee::getFirstName) is the standard way to build these chains now, it replaced the old anonymous inner class boilerplate almost entirely.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does try-with-resources actually do, and what happens if both the try block and the close() call throw?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Resource Management<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Any resource that implements AutoCloseable can be declared in the try&#8217;s parentheses, and the compiler generates code that closes it automatically when the block exits, whether that&#8217;s normal completion or an exception, closing resources in the reverse order they were declared. It replaces the old pattern of a resource declared outside the try and closed by hand in a finally block, which was verbose and easy to get wrong when there were multiple resources.<\/p>\n<p>If the try block throws and then close() also throws while cleaning up, the close() exception isn&#8217;t lost and it doesn&#8217;t silently replace the real one either, it gets attached to the primary exception as a suppressed exception, retrievable through getSuppressed(). That&#8217;s a real improvement over hand-written finally blocks before Java 7, where a throwing finally would just clobber whatever exception the try block had raised.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is type erasure, and what practical limitations does it put on Java generics?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Generics Erasure<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Generics exist purely at compile time for type checking, the compiler erases type parameters down to their bound (or Object if unbounded) in the actual bytecode. That&#8217;s why List&lt;String&gt; and List&lt;Integer&gt; are literally the same class at runtime, List.class, and it&#8217;s why you can&#8217;t write new T[], can&#8217;t do instanceof List&lt;String&gt;, and can&#8217;t overload two methods that only differ by generic type parameter.<\/p>\n<p>Erasure exists because generics were added in Java 5 and had to stay binary-compatible with pre-generics bytecode and libraries. The practical consequence is you can&#8217;t reliably reflect your way to a generic type&#8217;s element type at runtime unless it&#8217;s captured somewhere concrete, which is exactly the trick libraries like Gson use, a subclass anonymous class captures the type parameter in its own superclass signature, where it survives erasure because it&#8217;s now part of the class hierarchy rather than a local type parameter.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Can a Java enum have its own methods or constructor, and why would you want that?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Enum Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Yes, each enum constant is actually an instance of the enum class, not just a labeled integer, so the enum can have fields, an implicitly private constructor, regular methods, and even an abstract method that each constant implements differently. An Operation enum with ADD, SUBTRACT, and MULTIPLY constants, each supplying its own apply(int, int) body, is the textbook example, it replaces a switch statement with actual polymorphism.<\/p>\n<p>Because the JVM guarantees exactly one instance per constant per classloader, and enums are safe against reflection and serialization attacks that can break a hand-rolled singleton, Joshua Bloch&#8217;s Effective Java specifically recommends a single-element enum as the cleanest way to implement the Singleton pattern in Java.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between reduce() and collect() in the Streams API?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Streams API<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>reduce() folds a stream down to a single value using an associative accumulator function, it&#8217;s built for immutable folding, summing numbers or finding a max, and every step produces a new result instead of mutating shared state, which is exactly what makes it safe to run in parallel streams. collect() is for mutable reduction, it takes a Collector, made up of a supplier, accumulator, combiner, and sometimes a finisher, and it builds up a mutable container like a List or a Map, which is far cheaper for something like assembling a list than creating a brand new immutable result on every element.<\/p>\n<p>Rule of thumb: if you&#8217;re building a collection, use collect() with the built-in Collectors. Using reduce() over a mutable accumulator is a mistake that shows up in early Streams code, it breaks the no-side-effects contract the API expects and can produce genuinely wrong results when the stream is run in parallel.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does @Transactional propagation actually control, and where does it commonly break in real code?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring Transactions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Propagation controls what happens when a transactional method calls another transactional method. REQUIRED, the default, joins an existing transaction if one&#8217;s already open, or starts a new one if not. REQUIRES_NEW always suspends any outer transaction and starts a completely separate one, useful for something like audit logging that has to commit even if the surrounding business transaction later rolls back. NESTED uses savepoints so the inner method can roll back on its own without killing the whole outer transaction, but it only works with resource managers that actually support savepoints, like JDBC.<\/p>\n<p>The gotcha that trips up a lot of teams: @Transactional works through a proxy, so calling a @Transactional method from another method inside the same class bypasses the proxy entirely, the annotation is silently ignored and no transaction gets started. The fix is injecting the bean into itself, using AopContext.currentProxy(), or just pulling the method out into a separate bean.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are Spring&#039;s bean scopes, and how does a bean&#039;s lifecycle differ between them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring Bean Scope<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>singleton, the default, means one instance shared across the whole container, fine for stateless beans but dangerous if it accidentally holds per-request state. prototype creates a fresh instance every time it&#8217;s requested, but Spring only manages its creation, not its destruction, so a prototype bean holding resources that need cleanup has to handle that cleanup itself. request and session scopes are web-aware and tie a bean&#8217;s life to an HTTP request or session, useful for holding per-request data without threading it manually through every method call.<\/p>\n<p>On the lifecycle side, every bean goes through instantiation, dependency injection, then @PostConstruct (or afterPropertiesSet() from InitializingBean) before it&#8217;s ready to use. On shutdown, @PreDestroy (or destroy() from DisposableBean) runs, but only for singleton beans, since the container has no way of knowing when a prototype-scoped bean actually falls out of use in your own code.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Spring actually resolve a circular dependency between two beans, and should it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring DI<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>With constructor injection, a true circular dependency, Bean A needs B in its constructor, B needs A in its constructor, fails immediately at startup with a BeanCurrentlyInCreationException, since Spring can&#8217;t finish building either bean without the other one already existing. With field or setter injection Spring can actually resolve it, it instantiates A with its no-arg constructor, drops an early reference into its internal cache before A&#8217;s fields are populated, lets B look that reference up while B itself is being constructed, then comes back and finishes wiring A. This works because of a three-level cache Spring keeps specifically for this case inside the bean factory.<\/p>\n<p>That said, most senior engineers treat a resolvable circular dependency as a warning sign, not a feature to rely on. It usually means two beans are too tightly coupled, and the better fix is extracting a third interface or service both can depend on, or adding @Lazy on one of the injection points to break the cycle on purpose rather than leaning on the container&#8217;s internal plumbing.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem do Java Records solve, and what can&#039;t you do with one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Java Records<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Records, finalized in Java 16, are a compact syntax for immutable data carriers. record Point(int x, int y) {} alone generates private final fields, a canonical constructor, accessor methods named after the fields (x(), not getX()), and equals(), hashCode(), and toString() based on every component, none of which you write by hand.<\/p>\n<p>You can still add a compact constructor to validate or transform arguments before the implicit field assignment, add extra methods, and implement interfaces, but you can&#8217;t add extra instance fields beyond the declared components, can&#8217;t extend another class since records implicitly extend java.lang.Record, and can&#8217;t make the fields anything but final. They&#8217;re a strong fit for DTOs and API response objects where equality by value is the whole point, but a poor fit for JPA entities, which need a no-arg constructor and mutable fields records don&#8217;t allow.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nrecord Point(int x, int y) {\n\n    Point {\n\n        if (x &lt; 0 || y &lt; 0) {\n\n            throw new IllegalArgumentException(\u201ccoordinates must be non-negative\u201d);\n\n        }\n\n    }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are sealed classes in Java, and what do they actually buy you over a normal abstract class?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sealed Classes<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Sealed classes and interfaces, standard since Java 17, let you restrict exactly which classes are allowed to extend or implement a type, using sealed combined with a permits clause naming the allowed subtypes. Every permitted subtype has to be declared final, sealed itself to keep extending the restriction further down, or non-sealed to deliberately reopen it to unrestricted extension.<\/p>\n<p>The real payoff shows up with pattern matching in switch expressions. Because the compiler knows the complete, closed list of subtypes, a switch over a sealed type with no default branch is checked for exhaustiveness at compile time, so if someone adds a new permitted subtype later and forgets to update a switch handling it, the build fails instead of silently falling through at runtime, which plain inheritance and interfaces could never guarantee.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\nsealed interface Shape permits Circle, Square, Triangle {}\nfinal class Circle implements Shape {}\n\nfinal class Square implements Shape {}\n\nnon-sealed class Triangle implements Shape {}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does it actually take to design a truly immutable class in Java?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Immutability Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Declare the class final so nobody can subclass it into a mutable variant, make every field private final, skip setters entirely, and set everything through the constructor. That part is easy and most developers get it right.<\/p>\n<p>What people actually miss is a mutable field hiding inside an otherwise immutable class, a List, a Date, or an array. If you just store the reference you were handed, the caller who passed it in can mutate it after construction and silently break your object&#8217;s invariants, even though every field is marked final, since final only pins the reference itself, not whatever the reference points to. You have to defensively copy on the way in through the constructor and on the way out through the getter, or for collections specifically, wrap the copy in Collections.unmodifiableList(), or just return List.copyOf(), which handles both problems in one call.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">12<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through what actually happens inside a HashMap when you call put().<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Collections<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The key&#8217;s hashCode() runs first, then HashMap applies its own spreading function, XORing the hash with a right-shifted copy of itself, to reduce collisions from keys with similar low-order bits. That hash picks a bucket, an index into the internal array, via hash &#038; (capacity &#8211; 1). Each bucket holds a linked list of nodes by default.<\/p>\n<p>If a bucket&#8217;s list grows past 8 entries and the table has at least 64 buckets, Java 8+ converts that bucket into a red-black tree, so worst-case lookup drops from O(n) to O(log n). The default load factor is 0.75: the map resizes, doubles capacity and rehashes everything, once it&#8217;s 75% full. Most candidates know &#8220;HashMap uses hashing.&#8221; Fewer can explain why treeification exists or what a resize costs mid-request.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\n\/\/ simplified version of HashMap\u2019s internal hash spreading, java.util.HashMap\n\nstatic final int hash(Object key) {\n\n    int h;\n\n    return (key == null) ? 0 : (h = key.hashCode()) ^ (h &gt;&gt;&gt; 16);\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are virtual threads, and how are they different from platform threads?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A platform thread maps one-to-one to an OS thread, which is expensive enough (a couple of MB of stack space by default, plus OS scheduling overhead) that a service handling tens of thousands of concurrent blocking calls the old way runs out of threads before it runs out of anything else.<\/p>\n<p>A virtual thread, made permanent in Java 21 via <a href=\"https:\/\/openjdk.org\/jeps\/444\" target=\"_blank\" rel=\"noopener noreferrer\">JEP 444<\/a>, is a lightweight thread the JVM manages instead of the OS. The JVM multiplexes millions of virtual threads onto a small pool of OS threads, unmounting one from its carrier whenever it blocks on I\/O instead of leaving an OS thread idle. You write the same simple blocking-style code you always have, no reactive rewrite, and still scale to a large number of concurrent requests. The catch senior interviewers push on: virtual threads don&#8217;t help CPU-bound work, and heavy synchronized blocks or native pinning can stop one from unmounting, quietly reintroducing the scaling ceiling you were trying to escape.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A ThreadPoolExecutor&#039;s queue is full and every core thread is busy. What happens to the next submitted task?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>It depends entirely on the RejectedExecutionHandler configured on the pool, and plenty of candidates don&#8217;t know that&#8217;s even configurable. The default, AbortPolicy, throws a RejectedExecutionException immediately. CallerRunsPolicy pushes the task back to run on the calling thread, a built-in backpressure mechanism that slows down whatever&#8217;s submitting work. DiscardPolicy silently drops the task. DiscardOldestPolicy evicts the oldest queued task to make room for the new one.<\/p>\n<p>Production services almost never want the default silently throwing under load. CallerRunsPolicy is a common, deliberate choice specifically because it applies backpressure instead of failing outright, and it&#8217;s the answer that shows an interviewer you&#8217;ve actually tuned a pool under real load rather than just instantiated one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">G1GC or ZGC, which would you pick for a production service, and why?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">GC<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>I&#8217;d default to G1 unless there&#8217;s an actual measured pause-time problem, and I think a lot of teams reach for ZGC prematurely because the name sounds more impressive. G1 (the default collector since Java 9) divides the heap into regions and prioritizes collecting the regions with the most garbage first, targeting a pause-time goal, typically in the tens of milliseconds.<\/p>\n<p>ZGC targets sub-millisecond max pauses even on multi-terabyte heaps using colored pointers and load barriers, but that comes at a real CPU cost. Java 21 added <a href=\"https:\/\/openjdk.org\/jeps\/439\" target=\"_blank\" rel=\"noopener noreferrer\">Generational ZGC (JEP 439)<\/a> as an opt-in mode because the original single-generation ZGC didn&#8217;t exploit the same young-object-death pattern G1 already did, and it paid for that in extra CPU overhead on allocation-heavy workloads. Java 23 made generational mode the default, and Java 25 removed the non-generational mode entirely. If your service is an ordinary CRUD API and G1&#8217;s 50ms pauses have never once shown up in a complaint, switching to ZGC is solving a problem you don&#8217;t have yet.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Spring Boot&#039;s autoconfiguration actually decide which beans to create?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Spring Boot<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>@EnableAutoConfiguration, pulled in automatically by @SpringBootApplication, triggers Boot to read a list of autoconfiguration classes and each one is gated behind conditional annotations, most commonly @ConditionalOnClass and @ConditionalOnMissingBean. That&#8217;s the whole mechanism: Boot only wires a bean if the relevant library is actually on the classpath, and only if you haven&#8217;t already defined your own bean of that type.<\/p>\n<p>It&#8217;s why adding spring-boot-starter-data-jpa auto-configures a DataSource and an EntityManagerFactory, but only until you declare your own DataSource bean, at which point Boot backs off and uses yours instead. <a href=\"https:\/\/docs.spring.io\/spring-boot\/reference\/features\/developing-auto-configuration.html\" target=\"_blank\" rel=\"noopener noreferrer\">Spring&#8217;s own documentation on auto-configuration<\/a> covers this conditional chain in more depth than interviewers expect you to recite, but the underlying idea is what gets tested.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does ConcurrentHashMap actually achieve thread safety, and how has that mechanism changed across Java versions?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrent Collections<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>In Java 7, ConcurrentHashMap split the table into a fixed number of Segments, 16 by default, each essentially its own small lock, so writes to different segments could run in parallel but two writes landing in the same segment still blocked each other, while reads were mostly lock-free through volatile access. In Java 8 that design was scrapped entirely, the map is now backed by a single Node array, the same shape as a plain HashMap, and thread safety instead comes from CAS operations for the common case of inserting into an empty bin, combined with a synchronized block scoped to just that bin&#8217;s head node when there&#8217;s a collision to resolve, so lock granularity became per-bin rather than per-segment, which scales considerably better under real contention.<\/p>\n<p>Java 8 also added treeification, once a single bin&#8217;s linked list grows past 8 entries, and the table has at least 64 buckets, that bin converts into a red-black tree so worst-case lookup inside the bin drops from O(n) to O(log n), a defense specifically against hash-flooding attacks where an attacker crafts keys engineered to collide into the same bucket.<\/p>\n<p>size() and mappingCount() don&#8217;t lock anything at all, they sum a set of per-thread counter cells similar to LongAdder, and the result can be momentarily inconsistent under concurrent modification, which is an accepted tradeoff, an exact point-in-time size was never part of the contract for a genuinely concurrent collection.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Java has garbage collection, so how does a long-running service still end up with a memory leak?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory Leaks<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>GC only reclaims objects that are unreachable, so a Java memory leak is really an object graph that&#8217;s technically still reachable but nobody actually needs anymore. The classic case is a static collection, a Map or List that things keep getting added to but nothing ever removes from, since static fields are GC roots and live for the entire lifetime of the classloader, this shows up constantly in naive in-memory caches with no eviction policy.<\/p>\n<p>ThreadLocal is another repeat offender, especially in thread-pooled environments like servlet containers. If you set a value with ThreadLocal and never call remove(), that value stays attached to the pooled thread, which never actually dies, long after the request that created it finished, and in application-server contexts this can pin an entire webapp classloader in memory across a hot redeploy, because the ThreadLocalMap entry still references a class loaded by the old classloader. Listener and callback registration causes the same shape of problem, registering a listener on a long-lived object without ever unregistering it keeps that listener, and everything it closes over, alive indefinitely.<\/p>\n<p>None of these get fixed with a GC tuning flag, they&#8217;re fixed with discipline, bounded caches or a WeakHashMap where appropriate, always pairing ThreadLocal.set() with a try\/finally that calls remove(), and unregistering listeners on teardown. In a real incident, a heap dump analyzed in something like Eclipse MAT, looking at dominator trees and retained size per object, is how you actually figure out which of these it is.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is the classloader delegation model in Java, and why does it matter when debugging a strange ClassCastException?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Classloaders<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The bootstrap classloader, native code that loads the JDK&#8217;s own core classes, sits at the top, the platform classloader below it, and the application classloader that loads your own classpath below that. Any custom classloader (application servers like Tomcat give each deployed webapp its own) delegates upward first by default, a class load request travels all the way up to bootstrap and each loader gives its parent the first chance before trying to load the class itself. That&#8217;s the parent delegation model, and its whole purpose is guaranteeing that a core class like java.lang.String can never be shadowed by some rogue jar sitting on the classpath.<\/p>\n<p>The practical consequence is that class identity in Java isn&#8217;t just the fully qualified name, it&#8217;s the pair of classloader plus class name. If the same class gets loaded twice by two different classloaders, common in app servers running multiple webapps, in OSGi, or in plugin systems, you can get a ClassCastException that literally says something like com.foo.Bar cannot be cast to com.foo.Bar, which looks like a joke until you realize they&#8217;re two distinct Class objects loaded by two different loaders.<\/p>\n<p>This is also the exact mechanism behind classloader leaks after a hot redeploy. The old webapp&#8217;s classloader should become garbage once the webapp is undeployed, but if anything outside it still points to an object that classloader created, a ThreadLocal on a pooled thread, a JDBC driver still registered in DriverManager, a static field in a shared library, the entire old classloader, and every class and static field it ever loaded, stays pinned in memory, which is exactly why classloader leaks show up as slow, steady memory growth across repeated redeploys in app servers.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Production is hung and CPU is idle. How would you confirm and diagnose a deadlock?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Deadlock Debugging<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Start by capturing a thread dump, jstack on the pid, or kill -3 which prints straight to stdout, or jcmd&#8217;s Thread.print, and take it two or three times a few seconds apart so you can tell a genuine deadlock, identical stacks and states every time, from threads that are just slow but still making progress. The JVM actually helps here, if it detects a classic monitor deadlock, thread A holding lock 1 and waiting on lock 2 while thread B holds lock 2 and waits on lock 1, it prints a &#8220;Found one Java-level deadlock&#8221; section right in the dump, naming the exact threads and monitors involved.<\/p>\n<p>Where it gets harder is a deadlock that isn&#8217;t that clean two-thread cycle, thread pool exhaustion is the common one, every thread in a fixed pool ends up blocked waiting on a Future that can only be completed by another task that&#8217;s still sitting in that same pool&#8217;s queue, waiting for a free thread that will never come. The JVM won&#8217;t flag that as a deadlock at all, every thread just looks BLOCKED or WAITING on a queue with nothing moving, and you find it by reading what each individual thread&#8217;s stack is actually waiting on and tracing it back to the same exhausted executor.<\/p>\n<p>Once you&#8217;ve found the cycle, the real fix is almost always a consistent lock ordering across the whole codebase, or narrowing the critical section so one of the locks isn&#8217;t needed at all. In production you usually mitigate immediately with a restart, then follow up with a lock-ordering audit or switch bare synchronized blocks to tryLock() with a timeout so the app degrades instead of hanging the next time it happens.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do exceptions actually propagate through a chain of CompletableFuture calls, and how do you handle them correctly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Async Programming<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>If any stage in a CompletableFuture chain throws, that exception doesn&#8217;t flow into the next thenApply or thenCompose the way a normal thrown exception would, instead the future completes exceptionally and every dependent stage after it is skipped entirely until the chain hits a stage actually built to handle failure, exceptionally(), handle(), or whenComplete(). handle() and whenComplete() both run no matter what happened upstream, success or failure, but handle() lets you inspect the outcome and return a normal recovered value, while whenComplete() is a pure side-effecting callback, whatever it returns doesn&#8217;t replace the exception, the original result or exception keeps propagating right past it.<\/p>\n<p>A common bug is reaching for thenApply when thenCompose is what&#8217;s needed, if the function you pass itself returns a CompletableFuture, thenApply hands you back a CompletableFuture wrapping another CompletableFuture, which is almost never useful, thenCompose flattens that, it&#8217;s the exact same map versus flatMap distinction that shows up in Streams and Optional.<\/p>\n<p>And when you finally call get() or join() on the completed future, the original exception gets wrapped, get() throws a checked ExecutionException around the real cause, join() throws an unchecked CompletionException instead, and in both cases you need getCause() to see the actual root exception. Logging code that just calls e.getMessage() on the wrapper prints a useless generic message and buries the exception you actually care about.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is escape analysis in the JVM, and how does it let object allocation be avoided entirely?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">JIT Optimization<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Escape analysis is a JIT optimization performed by C2, the server compiler, once a method gets hot enough to be compiled, where the compiler works out whether an object created inside a method can ever be seen outside that method&#8217;s scope, escaping by being returned, stored into a field, handed to another method that might stash it somewhere, or touched by another thread. If the compiler can prove an object never escapes, it doesn&#8217;t have to allocate it on the heap at all, it can perform scalar replacement, breaking the object apart into its individual primitive fields and keeping those as plain local variables or registers instead.<\/p>\n<p>That means no allocation and no GC pressure from that object whatsoever, which is why small helper or wrapper objects created and thrown away inside a tight, hot loop often show near-zero allocation in a profiler once the JVM has warmed up, even though the code looks allocation-heavy on paper.<\/p>\n<p>The catch is that this only kicks in after the method has actually been JIT compiled by C2. Code measured cold, a short-lived CLI tool that never leaves the interpreter or C1, or a JMH benchmark without proper warmup iterations, shows the full allocation cost, which is exactly why microbenchmarking &#8220;is creating this object here expensive&#8221; with a stopwatch around a plain for loop gives you a wrong answer, and why real JVM benchmarking needs JMH with real warmup phases before the numbers mean anything.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Where does the Java string constant pool actually live in memory, and what does calling .intern() do?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">String Pool<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>This moved. Before Java 7, the string pool lived in PermGen, a fixed-size, GC-unfriendly region also used for class metadata, and interning large numbers of unique strings there was a genuine cause of OutOfMemoryError: PermGen space in production. From Java 7 onward the pool was relocated into the main heap, so interned strings became ordinary heap objects, collected normally once nothing references them, and PermGen itself was removed entirely in Java 8, replaced by Metaspace, which lives in native memory outside the heap and grows dynamically, bounded only if you set -XX:MaxMetaspaceSize, closing off a whole class of PermGen exhaustion bugs tied to dynamically generated classes from heavy reflection or proxy-based frameworks.<\/p>\n<p>Calling String.intern() looks the string up in the pool, if an equal string is already there it returns that canonical pooled reference, if not it adds this one and returns it. The real use case is parsing huge volumes of text with a lot of duplicate content, log parsing or CSV ingestion, where collapsing duplicate strings down to shared references genuinely saves memory. It&#8217;s not free though, every call does a pool lookup, and interning strings that don&#8217;t actually have meaningful duplication in your dataset just adds overhead for no benefit, so it should be a deliberate decision on a measured hot path, not something applied out of habit.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-callout iq-callout--insight not-prose\"><div class=\"iq-callout__title\">What we&#039;ve seen in Java mock interview sessions<\/div><div class=\"iq-callout__body\"><\/p>\n<p>Across Java-focused mock interviews run through LastRoundAI, the pattern that trips up candidates most isn&#8217;t a gap in language knowledge. It&#8217;s candidates who define volatile and synchronized correctly in isolation, then freeze the moment a follow-up connects the concept to a symptom: &#8220;your service&#8217;s response times spiked under load, walk through what you&#8217;d check first.&#8221; Naming a term is not the same skill as reasoning backward from a symptom to a cause, and interviewers notice the difference immediately.<\/p>\n<p>The second pattern shows up in the JVM and GC section: candidates who&#8217;ve memorized that &#8220;G1 is the default collector&#8221; but can&#8217;t explain why that matters for the service they&#8217;re being hired to run. At the senior level, that question isn&#8217;t a vocabulary check. It&#8217;s asking whether you&#8217;ve reasoned through a real pause-time or memory problem, not just read about one.<\/p>\n<p><\/div><\/div>\n<div class=\"iq-callout iq-callout--tip not-prose\"><div class=\"iq-callout__title\">Fresher versus experienced: same list, different weighting<\/div><div class=\"iq-callout__body\"><\/p>\n<p>Prepping for a campus or 0-2 year role, the kind TCS, Infosys, or Wipro run at scale? Put most of your remaining time into OOP, Collections, Exceptions, and Java 8+ basics, plus the two coding problems. Prepping for a 3+ year role instead? Weight your time toward Concurrency, JVM\/GC, and Spring Boot, since that&#8217;s where experienced loops actually separate candidates.<\/p>\n<p><\/div><\/div>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is Java still worth learning for a developer job in 2026?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. Java did not top Stack Overflow's 2025 Developer Survey technology rankings the way JavaScript, HTML\/CSS, and Python did, but it remains one of the most widely used languages in that survey, and it is still the default choice for enterprise backends across banking, insurance, and India's IT-services sector.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Do I need to know Spring Boot to get a Java developer job in India?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"For most product-based and mid-size company roles, yes. Spring Boot is the default framework for new Java backend work. For large IT-services companies hiring fresh graduates into legacy maintenance projects, it comes up less at the fresher stage and more once you're staffed onto a project that uses it.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Should I prepare with Java 8, Java 17, or Java 21 syntax?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Know Java 8's stream and lambda syntax cold first, since that is still the assumed baseline. Then add records, pattern matching for switch, and virtual threads, since Spring Boot 3 requires Java 17 as a minimum and interviewers increasingly ask about virtual threads specifically.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<div class=\"iq-related not-prose\"><div class=\"iq-related__title\">Related interview guides<\/div><div class=\"iq-related__grid\"><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/tcs\"><span class=\"iq-rel__t\">TCS Interview Questions (2026): NQT, Technical, MR &#038; HR Rounds<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/infosys\"><span class=\"iq-rel__t\">Infosys Interview Questions (2026): Online Test, Technical &#038; HR Rounds<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/wipro\"><span class=\"iq-rel__t\">Wipro Interview Questions (2026): NLTH, Technical &#038; HR Rounds<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/jpmorgan\"><span class=\"iq-rel__t\">JPMorgan Chase Software Engineer Interview Questions (2026)<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/goldman-sachs\"><span class=\"iq-rel__t\">Goldman Sachs Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/amazon-sde-2\"><span class=\"iq-rel__t\">Amazon SDE-2 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><\/div><\/div>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"nofollow noopener\">Stack Overflow Developer Survey 2025<\/a><\/li><li><a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/21\/gctuning\/z-garbage-collector.html\" target=\"_blank\" rel=\"nofollow noopener\">Oracle HotSpot GC Tuning Guide<\/a><\/li><li><a href=\"https:\/\/openjdk.org\/jeps\/444\" target=\"_blank\" rel=\"nofollow noopener\">OpenJDK JEP 444 (Virtual Threads)<\/a><\/li><li><a href=\"https:\/\/openjdk.org\/jeps\/439\" target=\"_blank\" rel=\"nofollow noopener\">OpenJDK JEP 439 (Generational ZGC)<\/a><\/li><li><a href=\"https:\/\/docs.spring.io\/spring-boot\/reference\/features\/developing-auto-configuration.html\" target=\"_blank\" rel=\"nofollow noopener\">Spring Boot: Developing Auto-configuration<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Oracle shipped Java 21 as a Long-Term Support release in September 2023, finalizing virtual threads and generational garbage collection. Almost three years later, a lot of java developer interview questions asked in India and the US are still written for Java 8. That gap is worth knowing before you walk in, since sounding fluent in&#8230;<\/p>\n","protected":false},"author":4,"featured_media":1643,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1158","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Java Developer Interview Questions (2026) | LastRoundAI<\/title>\n<meta name=\"description\" content=\"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\/GC, and Spring Boot. See what actually gets asked, and why.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/java-developer\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Java Developer Interview Questions (2026) | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\/GC, and Spring Boot. See what actually gets asked, and why.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/java-developer\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-java-developer-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"47 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer\",\"name\":\"Java Developer Interview Questions (2026) | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-java-developer-og.png\",\"datePublished\":\"2026-07-24T15:16:00+00:00\",\"description\":\"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\\\/GC, and Spring Boot. See what actually gets asked, and why.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-java-developer-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-java-developer-og.png\",\"width\":1200,\"height\":630,\"caption\":\"Java Developer interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/java-developer#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Java Developer Interview Questions (2026): Core Java to Spring Boot\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Java Developer Interview Questions (2026) | LastRoundAI","description":"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\/GC, and Spring Boot. See what actually gets asked, and why.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/java-developer","og_locale":"en_US","og_type":"article","og_title":"Java Developer Interview Questions (2026) | LastRoundAI","og_description":"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\/GC, and Spring Boot. See what actually gets asked, and why.","og_url":"https:\/\/lastroundai.com\/interview-questions\/java-developer","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-java-developer-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"47 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer","url":"https:\/\/lastroundai.com\/interview-questions\/java-developer","name":"Java Developer Interview Questions (2026) | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-java-developer-og.png","datePublished":"2026-07-24T15:16:00+00:00","description":"Real Java developer interview questions for 2026: Core Java, collections, concurrency, JVM\/GC, and Spring Boot. See what actually gets asked, and why.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/java-developer"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-java-developer-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-java-developer-og.png","width":1200,"height":630,"caption":"Java Developer interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/java-developer#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"Java Developer Interview Questions (2026): Core Java to Spring Boot"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1158","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1158"}],"version-history":[{"count":3,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1158\/revisions"}],"predecessor-version":[{"id":1751,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1158\/revisions\/1751"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1643"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1158"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1158"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}