Skip to content

Cookies on Jobverse

We use cookies that are needed to sign you in and keep the site working. With your permission we’d also use an analytics cookie to count job views, and Google advertising cookies to show ads. The site works the same whatever you choose. Read the Cookie Policy.

Jobverse
Sign in

Java Full Stack Developer Top 100 Interview Questions

100 Java full stack developer interview questions with structured answers, comparison tables, flow diagrams and runnable examples. Covers core Java, concurrency, backend architecture, REST APIs, SQL, persistence, HTML, CSS, JavaScript, security, testing, deployment and system design.

100 questions

Questions 21-40 of 100

  1. Q21.How would you count word frequencies while preserving first-seen order in Java?

    I would normalize each accepted token and accumulate counts in a LinkedHashMap. Its insertion-order behavior makes the displayed result follow the first appearance of each normalized word.

    • Input contract: decide how text is tokenized and whether case, punctuation, or Unicode normalization makes two words equivalent before counting them.
    • Aggregation: use merge to add one to the existing count or create an initial count when the normalized word is first encountered.
    • Complexity: expected map work is linear in token count, with normalization and hashing also depending on token lengths; memory grows with unique words.
    • Validation: define behavior for missing or blank tokens and choose a count type large enough for the intended input volume.

    An ordinary hash map would count correctly but would not establish the required presentation order.

    Example

    This example receives already-tokenized words and treats uppercase and lowercase versions as the same word.

    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Locale;
    import java.util.Map;
    import java.util.Objects;
    
    class Main {
        static Map<String, Long> frequencies(List<String> words) {
            Map<String, Long> counts = new LinkedHashMap<>();
            for (String word : words) {
                Objects.requireNonNull(word, "word");
                if (word.isBlank()) throw new IllegalArgumentException("blank word");
                String key = word.toLowerCase(Locale.ROOT);
                counts.merge(key, 1L, Long::sum);
            }
            return counts;
        }
        public static void main(String[] args) {
            System.out.println(frequencies(List.of("Java", "API", "java", "SQL", "api")));
        }
    }
    // Expected output: {java=2, api=2, sql=1}
  2. Q22.How would you find the first non-repeating Unicode code point in a Java string?

    I would count Unicode code points in encounter order and return the first one with a count of one. Iterating only over char values can split supplementary characters into surrogate halves.

    • Representation: use the string’s code-point sequence when the requirement is about Unicode code points rather than individual UTF-16 code units.
    • Counting: preserve first appearance in an ordered map so the final scan returns the earliest unique value without sorting by numeric code point.
    • Result: return an explicit optional result when the string is empty or every code point repeats, rather than inventing a sentinel character.
    • Boundary: a user-perceived character can contain several code points, so grapheme-cluster requirements need a different segmentation rule.

    Expected processing is linear in the code-point count, with memory proportional to distinct code points encountered.

    Example

    The supplementary character is returned as one code point instead of being split into two char values.

    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.OptionalInt;
    
    class Main {
        static OptionalInt firstUnique(String text) {
            Objects.requireNonNull(text, "text");
            Map<Integer, Integer> counts = new LinkedHashMap<>();
            text.codePoints().forEach(point -> counts.merge(point, 1, Integer::sum));
            return counts.entrySet().stream()
                    .filter(entry -> entry.getValue() == 1)
                    .mapToInt(Map.Entry::getKey)
                    .findFirst();
        }
        public static void main(String[] args) {
            int point = firstUnique("a😀abb").orElseThrow();
            System.out.println(new String(Character.toChars(point)));
        }
    }
    // Expected output: 😀
  3. Q23.How would you remove duplicates from a Java list while preserving encounter order?

    I would use an insertion-ordered set to retain the first occurrence of each equal value, then return a new list. The equality policy must match what the application means by duplicate.

    • Identity: for objects, confirm that equals and hashCode represent the intended business identity; identical-looking objects can still compare differently.
    • Order: insertion order preserves the first accepted occurrence, while a sorted set would impose a different ordering and could use different comparison semantics.
    • Ownership: create a new result when callers should keep their original list unchanged, and specify whether the returned list may be modified.
    • Complexity: expected time is linear in input size with appropriate hashing, and extra memory is proportional to unique values.

    For duplicates by one field rather than whole-object equality, use an explicit keyed selection policy instead of assuming a general set solves the requirement.

    Example

    This program returns the first occurrence of each integer and leaves the input list unchanged.

    import java.util.ArrayList;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Objects;
    
    class Main {
        static <T> List<T> uniqueInOrder(List<T> values) {
            Objects.requireNonNull(values, "values");
            return new ArrayList<>(new LinkedHashSet<>(values));
        }
        public static void main(String[] args) {
            List<Integer> values = List.of(3, 1, 3, 2, 1);
            System.out.println(uniqueInOrder(values));
        }
    }
    // Expected output: [3, 1, 2]
  4. Q24.How would you sort Java objects by several fields with deterministic tie-breaking?

    I would build a comparator that states each ordering rule explicitly, from the primary field to a stable final tie-breaker. This makes the required result easier to verify than a hand-written chain of arithmetic comparisons.

    • Primary order: select the appropriate comparison method for the field type, and reverse only the portion of the comparator that should descend.
    • Secondary order: add thenComparing rules for names or other fields, specifying case sensitivity and missing-value handling when relevant.
    • Determinism: include a stable unique identifier when otherwise equal values must have a reproducible total order.
    • Safety: avoid subtracting numeric fields to compare them because subtraction can overflow, and clarify whether sorting may mutate the caller’s list.

    Comparison rules should remain consistent during sorting, so do not mutate the ordering fields concurrently.

    Example

    This Java 17 example sorts by salary units descending, then name ascending, then unique identifier.

    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    
    class Main {
        record Employee(int id, String name, int salary) {}
    
        public static void main(String[] args) {
            List<Employee> employees = new ArrayList<>(List.of(
                    new Employee(1, "Chen", 100),
                    new Employee(2, "Ben", 200),
                    new Employee(3, "Ada", 100)));
            employees.sort(Comparator.comparingInt(Employee::salary).reversed()
                    .thenComparing(Employee::name)
                    .thenComparingInt(Employee::id));
            employees.forEach(employee -> System.out.println(employee.name()));
        }
    }
    // Expected output:
    // Ben
    // Ada
    // Chen
  5. Q25.When is Optional useful, and what mistakes should you avoid?

    Optional can make the possible absence of a return value explicit. It is most useful when callers should deliberately choose how to handle a missing result.

    • Construction: use ofNullable for a potentially missing reference and of only when a non-NULL value is guaranteed; an Optional reference itself should not be NULL.
    • Transformation: use map and flatMap to compose present values, rather than repeatedly checking presence and calling get without a clear absence policy.
    • Defaults: orElse evaluates its argument before the call, while orElseGet evaluates its supplier only when the value is absent, which matters for expensive or side-effecting defaults.
    • Boundaries: use a meaningful exception or alternate result when absence is exceptional, and avoid introducing Optional into every field without considering persistence and serialization contracts.

    Optional does not validate the business correctness of a present value or replace ordinary error handling.

  6. Q26.How do Java records differ from ordinary classes?

    A record concisely declares a transparent carrier for a fixed set of components. It supplies component accessors and value-oriented methods, but its immutability is shallow.

    RecordOrdinary class
    State declared as fixed record componentsState in flexible instance fields
    Accessors, equals, hashCode and toString generatedThese methods usually written or generated separately
    Final, and extends the record base classCan use supported class inheritance

    When to use which:

    • Use a record for a value or transfer representation whose components form an appropriate public description of the object’s state.
    • Use an ordinary class when identity, evolving mutable state, inheritance, or a deliberately hidden representation is central to the design.
    • Defensively copy mutable components when callers must not mutate a record’s logical contents through a retained collection or array reference.

    A final component reference does not freeze the referenced object. Records can validate construction and implement interfaces, but they are not a universal replacement for domain entities or persistence models.

  7. Q27.How do volatile, synchronized, and atomic variables differ?

    These mechanisms solve different concurrency problems. Visibility of a changed value, exclusive execution, and atomic updates are related requirements but are not interchangeable.

    volatilesynchronizedAtomic variable
    Visibility and ordering for the variableMutual exclusion and monitor orderingSupported atomic operations
    Compound updates are not atomicProtects only operations that use the same lockDoes not coordinate unrelated invariants automatically

    When to use which:

    • Use volatile for suitable publication or state-signaling patterns when each operation is independently sufficient and no compound invariant requires a lock.
    • Use synchronized when several reads and writes must execute together while preserving an invariant, with consistent access through the same monitor.
    • Use an atomic variable for supported updates such as compare-and-set or increment, considering contention and retry behavior of update functions.

    Making a reference volatile does not make the referenced object’s fields thread-safe. Similarly, incrementing a volatile integer still involves separate read, calculation, and write steps.

  8. Q28.How would you combine two independent asynchronous Java operations?

    I would start both operations and compose their completion stages, rather than waiting for the first before starting the second. Concurrency needs explicit resource and failure policies.

    1. Confirm that the operations are independent and select an executor suitable for their workload, with bounded capacity or appropriate resource limits.
    2. Start both operations and combine their results with a completion-stage operation that waits for both values without blocking an intermediate application thread.
    3. Define deadlines and exception handling, remembering that timing out a future does not necessarily interrupt or cancel the underlying work.
    4. Wait only at an intentional outer boundary if needed, then release owned executor resources and avoid sharing mutable result state unsafely.
    flowchart TD
    A["Start independent work"]
    B["Compute first result"]
    C["Compute second result"]
    D["Compose both completions"]
    E{"Completed successfully?"}
    F["Return combined value"]
    G["Handle failure or deadline"]
    A --> B
    A --> C
    B --> D
    C --> D
    D --> E
    E -->|yes| F
    E -->|no| G

    Trade-offs:

    • Parallel calls can reduce waiting time but increase simultaneous pressure on databases and downstream services.
    • Blocking a limited executor while waiting for work scheduled on that same executor can create starvation.

    Example

    This program starts two tasks, combines their values, and waits only for the final composed result.

    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    class Main {
        public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(2);
            try {
                CompletableFuture<Integer> left = CompletableFuture.supplyAsync(() -> 21, pool);
                CompletableFuture<Integer> right = CompletableFuture.supplyAsync(() -> 2, pool);
                int result = left.thenCombine(right, (a, b) -> a * b)
                        .orTimeout(2, TimeUnit.SECONDS).join();
                System.out.println(result);
            } finally {
                pool.shutdown();
            }
        }
    }
    // Expected output: 42
  9. Q29.When do virtual threads help compared with platform threads?

    Virtual threads, available as a standard feature from Java 21, make thread-per-task code practical for many waiting operations. They do not make a CPU-bound calculation intrinsically faster.

    Platform threadVirtual thread
    Typically backed by an operating-system threadScheduled onto carrier threads
    Higher cost per simultaneously blocked taskStill limited by CPU and external resources

    When to use which:

    • Use virtual threads for suitable blocking I/O workloads with many independent tasks, while limiting scarce resources such as database connections separately.
    • Use an appropriately bounded execution strategy for CPU-heavy work, because increasing runnable thread count beyond useful CPU parallelism can add overhead.
    • Check runtime-specific limitations and profile real behavior; monitor-related pinning changed in Java 24, so older guidance about synchronized blocks is not universally applicable.

    Do not pool virtual threads merely to reuse them. Control workload admission and resource concurrency explicitly, and review thread-local memory when very many tasks can exist.

  10. Q30.How would you investigate a Java memory leak or repeated out-of-memory failure?

    I would identify which resource is exhausted and whether retained data grows beyond the workload’s expected live set. Garbage collection cannot remove objects that remain reachable through unintended references.

    1. Collect the failure category, heap and native-memory trends, garbage-collection behavior, workload changes, and recent releases rather than assuming every failure is a heap leak.
    2. Inspect representative memory evidence for dominant object types, retention paths, thread growth, buffers, caches, and unclosed resources.
    3. Correct the retaining owner or unbounded growth, such as a cache without eviction, a listener never removed, or a queue whose producers exceed consumers.
    4. Reproduce the workload after the fix and confirm stable retained memory and acceptable latency over a meaningful duration before adjusting capacity.
    flowchart TD
    A["Identify exhausted resource"]
    B["Measure retained growth"]
    C{"Growth exceeds expected live data?"}
    D["Trace retaining owner"]
    E["Review capacity and allocation rate"]
    F["Fix and verify sustained behavior"]
    A --> B
    B --> C
    C -->|yes| D
    C -->|no| E
    D --> F
    E --> F

    Trade-offs:

    • A larger heap may delay failure while increasing pauses or cost; it does not repair an unbounded retention path.
    • Capturing detailed memory evidence consumes resources, so choose a collection method appropriate for the running service.

    Example

    A static map stores every search query and its result with no eviction.

    1. Retained entries increase with distinct queries even after requests finish.
    2. A heap inspection shows the map retaining most result objects.
    3. Add a justified size and expiry policy or remove the unnecessary cache.
    4. Run sustained traffic and confirm the retained set reaches a stable bound.
  11. Q31.What are inversion of control and dependency injection?

    Inversion of control moves responsibility for some control flow or object assembly outside the component itself. Dependency injection is a technique that supplies a component’s collaborators rather than having it construct or locate them internally.

    • Explicit dependencies: a service declares the capabilities it needs, such as a repository or clock, so its requirements are visible to callers and tests.
    • Assembly: an application configuration or container chooses concrete implementations and connects them at an appropriate composition boundary.
    • Separation: business logic can depend on focused contracts while infrastructure details, configuration values, and object lifecycles are managed separately.
    • Testing: controlled collaborators can replace network or persistence dependencies for focused tests without changing the production service’s core logic.

    Injection does not automatically produce good design. Circular dependencies, oversized interfaces, and hidden global state can still make the application difficult to understand.

  12. Q32.How do constructor, setter, and field injection differ?

    These approaches supply dependencies at different points in an object’s lifecycle. The choice affects visibility, valid construction, mutability, and how easily the object can be tested directly.

    ConstructorSetterField
    Supplied as construction parametersSupplied by a later method callSupplied by direct field assignment from infrastructure
    Required state is set up immediatelySupports deliberate optional or changeable dependenciesCan hide requirements and complicate direct construction

    When to use which:

    • Use constructor injection for required collaborators so an object cannot normally be created without them and final dependency references can be used where appropriate.
    • Use setter injection when later configuration or replacement is an intentional part of the contract, validating that the object remains usable before operations run.
    • Use field injection only with a clear reason and awareness of its lifecycle and testing costs, rather than relying on invisible dependencies by default.

    A very large constructor often signals excessive responsibility. Splitting responsibilities is usually clearer than merely hiding the parameter list through another injection style.

  13. Q33.What happens when an HTTP request reaches a Java web application?

    A web request passes through transport and application boundaries before producing a response. Understanding the path helps place security, validation, business logic, and diagnostics correctly.

    1. The server accepts and parses the request, applies configured limits, and enters relevant filters or middleware for cross-cutting request handling.
    2. Authentication, authorization, and routing determine whether the request may reach the selected handler; some failures return before controller execution.
    3. The handler validates the input contract and invokes business services, which coordinate persistence or other dependencies through defined boundaries.
    4. The application maps the result or error into an HTTP response and records useful operational context without exposing internal implementation details.

    Filters, handler interceptors, and service logic operate at different scopes. Choose the narrowest correct boundary and ensure early failures still receive appropriate security and diagnostic handling.

    flowchart TD
    A["Receive and parse request"]
    B["Apply request filters"]
    C{"Access permitted?"}
    D["Route and validate input"]
    E["Execute business service"]
    F["Use persistence or dependencies"]
    G["Serialize HTTP response"]
    H["Return controlled rejection"]
    A --> B
    B --> C
    C -->|yes| D
    C -->|no| H
    D --> E
    E --> F
    F --> G
  14. Q34.Where should transaction boundaries belong in a layered Java application?

    I would place a transaction around the business operation whose database changes must succeed together. That boundary commonly belongs in a service rather than being scattered across individual repository calls.

    1. Identify the business invariant, required reads and writes, and the database resources that must participate in the same transaction.
    2. Choose appropriate isolation and concurrency control, and configure rollback rules deliberately rather than assuming every exception causes the same outcome.
    3. Keep the transaction focused and avoid holding database resources while waiting for slow unrelated network calls or user interaction.
    4. Commit before reporting durable success, and coordinate external effects through a suitable pattern when they cannot participate in the database transaction.
    flowchart TD
    A["Validate business request"]
    B["Enter service transaction"]
    C["Apply related database changes"]
    D{"Operation succeeds?"}
    E["Commit durable result"]
    F["Roll back and map failure"]
    G["Return controlled response"]
    A --> B
    B --> C
    C --> D
    D -->|yes| E
    D -->|no| F
    E --> G
    F --> G

    Trade-offs:

    • A broader transaction simplifies some invariants but can hold locks and connections longer, reducing concurrency.
    • A local transaction cannot automatically make a remote payment or message publication atomic with its database writes.

    Example

    Creating an order requires an order row and its line items.

    1. Validate the request before beginning avoidable database work.
    2. Insert the header and all required lines within one transaction.
    3. Roll back the operation if a required line fails validation or persistence.
    4. Return success only after commit, with external notifications handled through a coordinated delivery path.
  15. Q35.Why can a declarative transaction fail to apply in a proxy-based application?

    In proxy-based transaction management, behavior applies when a call passes through the configured proxy. A transaction declaration alone does not guarantee that every possible invocation crosses that boundary.

    • Self-invocation: a method calling another method directly on the same object can bypass the proxy, so the inner declaration may not start or alter a transaction as expected.
    • Object ownership: manually constructed objects may not be managed or wrapped by the configured infrastructure, leaving interception absent.
    • Method and proxy constraints: visibility, final methods, interface exposure, and proxy mechanism can affect which methods are intercepted; verify the actual configuration.
    • Context changes: work moved to another thread does not automatically inherit a thread-bound transaction, and rollback behavior also depends on configured exception rules.

    Test the effective transaction at the real entry point. Refactor the boundary when needed instead of relying on a declaration that the invocation path never observes.

  16. Q36.How do object scopes affect thread safety in a Java web application?

    Scope determines how long an object lives and which callers share it. It does not automatically determine whether the object’s mutable state is safe for concurrent access.

    • Application or singleton scope: one managed instance may serve many requests, so request-specific values should not be stored in ordinary shared mutable fields.
    • Request scope: an instance belongs to one request lifecycle, but asynchronous work and escaped references can still complicate ownership and concurrency.
    • Prototype or per-creation scope: new instances can reduce sharing, while dependency injection and retention determine which callers actually reuse each instance.
    • Design choice: prefer stateless services or explicit immutable state, and coordinate genuinely shared state with suitable concurrency mechanisms and lifecycle rules.

    A singleton managed by a container is not necessarily a JVM-wide singleton. Also, injecting one short-lived object into a long-lived object may capture that particular instance unless an appropriate lookup mechanism is used.

  17. Q37.How do server-rendered page controllers and JSON API controllers differ?

    Both handle requests and invoke application behavior, but their response contracts serve different consumers. Rendering a page and returning structured data are presentation choices, not reasons to duplicate business rules.

    Server-rendered pageJSON API
    Responds with HTML from a model and templateResponds with structured resource or operation data
    Consumed by browser navigationConsumed by a client application or another service

    When to use which:

    • Use server-rendered pages when the server should deliver ready-to-display markup and the interaction model benefits from ordinary navigation and progressive enhancement.
    • Use JSON APIs when clients need structured data for dynamic interfaces, mobile applications, or integration with other systems.
    • Share business services where appropriate while keeping validation, response serialization, and view concerns explicit at the presentation boundary.

    Returning JSON alone does not establish a fully RESTful API. Resource modeling, HTTP semantics, caching, and consistent error behavior still need deliberate design.

  18. Q38.How would you validate request DTOs without exposing persistence entities directly?

    I would define a request contract that accepts only supported client inputs and validate it before applying business rules. Persistence entities often expose fields and relationships that do not belong in the public API.

    1. Define explicit request and response representations, including required fields, sizes, formats, and which attributes the caller is allowed to control.
    2. Validate structural and cross-field constraints, then map accepted inputs into domain operations rather than copying arbitrary client properties into stored entities.
    3. Check business rules and authorization against current trusted state, including server-owned prices, tenant identity, and concurrency-sensitive conditions.
    4. Return consistent field-level errors where appropriate and expose only the response data needed by the authorized consumer.
    flowchart TD
    A["Receive request DTO"]
    B["Validate structure and field rules"]
    C{"Input accepted?"}
    D["Check business rules and access"]
    E["Apply domain operation"]
    F["Map response DTO"]
    G["Return validation errors"]
    A --> B
    B --> C
    C -->|yes| D
    C -->|no| G
    D --> E
    E --> F

    Trade-offs:

    • Separate DTOs add mapping code but prevent accidental coupling between database structure and client contracts.
    • Client-side validation improves feedback, while server-side validation remains necessary for every untrusted request.

    Example

    A checkout request contains a product identifier, quantity, and a client-supplied discount flag.

    1. Accept only the documented input fields and reject or ignore unsupported fields according to the contract.
    2. Validate the quantity and locate the authorized product.
    3. Calculate price and discount eligibility from trusted server rules.
    4. Return the resulting order representation without exposing internal entity fields.
  19. Q39.How would you design consistent exception handling for a REST API?

    I would translate failures at a shared HTTP boundary into a documented error contract. Business services should express meaningful failures without deciding every transport detail themselves.

    1. Classify validation, authentication, authorization, missing-resource, conflict, dependency, and unexpected failures according to the public API contract.
    2. Map each category to an appropriate status and stable machine-readable error code, adding safe details and field errors when they help the client recover.
    3. Record diagnostic context and a correlation identifier internally while excluding secrets, raw database errors, and stack traces from ordinary client responses.
    4. Test errors arising before and after controller execution, ensuring committed work is not reported as if it were safely rolled back.
    flowchart TD
    A["Failure reaches HTTP boundary"]
    B{"Expected application failure?"}
    C["Map documented status and code"]
    D["Record unexpected diagnostic context"]
    E["Build safe error representation"]
    F["Return response with correlation identifier"]
    A --> B
    B -->|yes| C
    B -->|no| D
    C --> E
    D --> E
    E --> F

    Trade-offs:

    • Detailed validation errors improve client recovery, but excessive internal detail can expose implementation or account information.
    • A single error format simplifies clients only when individual status and error-code meanings remain precise.

    Example

    An update fails because another request already changed the resource version.

    1. Classify it as a documented concurrency conflict rather than an unexplained server error.
    2. Return the chosen conflict status and a stable code.
    3. Include safe guidance for reloading or retrying with a current version.
    4. Log the technical cause with the same correlation identifier.
  20. Q40.How do HTTP method safety and idempotency differ?

    A safe method is intended to be read-only from the client’s perspective. An idempotent method has the same intended server effect when repeated with the same request, even if responses differ.

    GET and HEADPUT and DELETEPOST and PATCH
    Safe: no intended state changeNot safe: they change stateNot safe: they change state
    IdempotentIdempotent by defined semanticsNot idempotent in general

    When to use which:

    • Use GET or HEAD for retrieval semantics, avoiding state-changing business operations that would make link previews, retries, or caches unexpectedly perform actions.
    • Use PUT or DELETE according to their defined resource semantics, recognizing that a repeated delete can return a different status while the resource remains deleted.
    • Use POST or PATCH when their semantics fit, adding application-level duplicate protection when a retry must not create another business effect.

    Safe does not mean that the server performs no logging or bookkeeping. Idempotency also does not mean the response body, timestamp, or request count must remain identical.