30 Java Scenario-Based Interview Questions

Naveen AnamaneniUpdated · 22 min read

A doctor's exam doesn't only ask what a disease is. It hands the student a patient: "fever for three days, a rash on the arms, just back from a trip. What do you do?" Interviews for Java developers have moved the same way. Alongside "what is a HashMap?" you will hear "our report shows the same customer twice; why?" These are scenario questions, and they test whether you can apply what you know to a real problem. The 30 scenarios on this page come from the kind of projects Java runs every day: banking, online shopping, payments and production support.

How to Answer Any Scenario

A strong answer follows the same five steps, and every model answer on this page uses them:

  1. Restate the problem in one sentence, so the interviewer knows you understood it.
  2. Name the root cause, not just the symptom.
  3. Give the fix, with a few lines of code if it helps.
  4. Say how you would stop it happening again: a test, monitoring, or a code review rule.
  5. Mention the trade-offs, such as what changes when the app runs on several servers.

Easy Scenarios (S1 to S10)

Everyday coding bugs that any developer meets in the first year on a project.

S1. Invoice Total That Doesn't Add Up

Scenario: A billing app adds item prices stored as double. A customer's invoice shows 0.30000000000000004, and the tax total is a cent off.

Question: What is wrong, and how do you fix it?

Show a strong answer
  • Cause: double is binary floating point; values such as 0.1 can't be stored exactly, so errors build up.
  • Fix: use BigDecimal, created from strings, with explicit rounding:
Java
BigDecimal total = new BigDecimal("0.10")
        .add(new BigDecimal("0.20"));                    // 0.30
BigDecimal withTax = total.multiply(new BigDecimal("1.18"))
        .setScale(2, RoundingMode.HALF_UP);              // 0.35
  • Also: compare amounts with compareTo, not equals (which also compares scale). Some systems store money as long cents instead.
  • Prevent: a team rule of "no double for money", plus unit tests with known tricky values.

Revise: BigInteger and BigDecimal in Java.

S2. NullPointerException in the Profile Page

Scenario: Production logs show NullPointerException at user.getAddress().getCity() for some users who never entered an address.

Question: How do you fix it properly, not just with an if?

Show a strong answer
  • Read the message: helpful NPE messages (Java 14+) say exactly which part was null, for example because the return value of "User.getAddress()" is null.
  • Fix at the right level: decide whether "no address" is valid. If it is, model it: Optional<Address> getAddress(), and:
Java
String city = user.getAddress()
        .map(Address::city)
        .orElse("Not provided");
  • If it isn't valid, validate when the user is created (Objects.requireNonNull) so bad data never gets in.
  • Prevent: a unit test with a user who has no address; avoid returning null from methods.

Revise: Helpful NullPointerException Messages and Optional Class in Java.

S3. Login Works on One Laptop but Not in Production

Scenario: A junior developer wrote if (inputPassword == storedPassword). Sometimes valid logins fail.

Question: What are the bugs here, functional and security?

Show a strong answer
  • Functional bug: == compares references. Strings that came from the database or a request are different objects, so it fails. Use equals.
  • Security bug: passwords must never be stored or compared in plain text. Store a salted, slow hash (for example PBKDF2 via the JDK, or bcrypt/Argon2) and compare hashes.
  • Compare hash bytes in constant time with MessageDigest.isEqual(a, b) to avoid timing attacks.
  • Prevent: a code review checklist entry ("no == on strings"), and static analysis (SonarLint flags it).

Revise: String Comparison in Java and Storing Passwords Safely in Java.

S4. Duplicate Customers in a Report

Scenario: A nightly job collects customers into a HashSet<Customer> to remove duplicates, but the report still lists the same customer twice.

Question: Why, and how do you fix it?

Show a strong answer
  • Cause: Customer doesn't override equals and hashCode, so the set compares object identity. Two objects with the same data count as different.
  • Fix: decide what makes two customers the same (for example, the email address) and implement equals and hashCode on exactly those fields. If all fields define identity, a record does it for you.
  • Watch out: never change those fields while the object is in a set or map.
  • Prevent: a unit test that adds two equal customers and expects a set of size 1.

Revise: Object Equality and Hash Codes in Java.

S5. Canceling Orders Crashes the Job

Scenario: A cleanup job loops over List<Order> with a for-each loop and calls orders.remove(o) for canceled orders. It throws ConcurrentModificationException.

Question: Fix it.

Show a strong answer
  • Cause: for-each uses a fail-fast iterator; changing the list directly during the loop breaks it.
  • Fix:
Java
orders.removeIf(o -> o.status() == Status.CANCELED);
  • Or build a new list with a stream filter. If many threads share the list, a concurrent collection is needed instead.
  • Prevent: prefer removeIf or streams over manual removal in loops.

Revise: ConcurrentModificationException in Java.

S6. Log Analyzer Runs Out of Memory

Scenario: A tool reads a 2 GB server log with Files.readAllLines(path) to count ERROR lines, and dies with OutOfMemoryError: Java heap space.

Question: How do you process the file?

Show a strong answer
  • Cause: readAllLines loads every line into memory at once.
  • Fix: stream the file lazily, one line at a time, and close it:
Java
try (Stream<String> lines = Files.lines(path)) {
    long errors = lines.filter(l -> l.contains("ERROR")).count();
}
  • A BufferedReader loop works too. Memory use stays small no matter how big the file is.
  • Prevent: treat input size as unbounded; test with a large file.

Revise: How to Read a File in Java.

S7. Meeting Invites Show the Wrong Time

Scenario: A scheduling app stores meeting times as LocalDateTime. Users in London and Bengaluru see different, wrong times for the same meeting.

Question: What is the right way to store and show times?

Show a strong answer
  • Cause: LocalDateTime has no time zone, so it isn't a real moment in time.
  • Fix: store an Instant (UTC), and convert only for display:
Java
Instant start = meeting.start();
ZonedDateTime forUser = start.atZone(ZoneId.of("Asia/Kolkata"));
  • Store the organizer's ZoneId too, for recurring meetings across daylight-saving changes.
  • Prevent: a rule of "UTC in storage, local time only in the UI"; tests that cross a daylight-saving change.

Revise: ZonedDateTime, ZoneId and Instant.

S8. Database Password in the Source Code

Scenario: The DB URL and password are hard-coded in a Java class, and the app must run in dev, test and production.

Question: How should configuration work?

Show a strong answer
  • Read settings from outside the code: environment variables, with a properties file for defaults:
Java
String url = System.getenv()
        .getOrDefault("DB_URL", props.getProperty("db.url"));
  • Never commit secrets to Git; use a secrets manager or the deployment platform's secret store, and rotate the leaked password.
  • Prevent: secret scanning in CI, and one config class that validates required settings at startup and fails fast.

Revise: System Class in Java and Reading and Writing Properties Files.

S9. "Too Many Open Files"

Scenario: After a few hours, an import service fails with IOException: Too many open files.

Question: What is the likely cause and fix?

Show a strong answer
  • Cause: files, sockets or connections are opened but not closed, especially when exceptions skip the close() call.
  • Fix: use try-with-resources for everything that is AutoCloseable:
Java
try (BufferedReader reader = Files.newBufferedReader(path)) {
    // read
}
  • Check: count open handles (lsof -p <pid>) to confirm the leak.
  • Prevent: static analysis rules for unclosed resources, and code review.

Revise: Try-With-Resources in Java.

S10. CSV Export Takes Minutes

Scenario: An export builds a 100,000-row CSV with csv += row + "\n"; in a loop, and it takes minutes.

Question: Why is it slow, and what do you use instead?

Show a strong answer
  • Cause: strings are immutable, so each += copies everything built so far: O(n²) work.
  • Fix: use a StringBuilder, or better, a stream with Collectors.joining, or write directly to a file with a BufferedWriter:
Java
String csv = rows.stream()
        .map(Row::toCsvLine)
        .collect(Collectors.joining("\n"));
  • For very large exports, write row by row to the output stream instead of building one big string.
  • Prevent: a performance test with realistic data size.

Revise: StringBuilder in Java.

Medium Scenarios (S11 to S20)

Design and data handling: sorting, grouping, APIs, databases and caches.

S11. Product Listing Sort Order

Scenario: An e-commerce page must list products by price (low to high), then rating (high to low), then name (A to Z).

Question: Write the sort.

Show a strong answer
Java
products.sort(Comparator.comparing(Product::price)
        .thenComparing(Product::rating, Comparator.reverseOrder())
        .thenComparing(Product::name));
  • Mention null handling (Comparator.nullsLast) and that sorting on large catalogs usually belongs in the database query (ORDER BY) with pagination.

Revise: Comparator Chaining in Java.

S12. Revenue per Customer

Scenario: The finance team wants total order value per customer from a list of Order(customerId, amount), where amount is a BigDecimal.

Question: Write it with streams.

Show a strong answer
Java
Map<String, BigDecimal> totals = orders.stream()
        .collect(Collectors.groupingBy(Order::customerId,
                Collectors.reducing(BigDecimal.ZERO, Order::amount,
                        BigDecimal::add)));
  • Mention why not summingDouble (money precision), and that for millions of rows a SQL GROUP BY is usually better than loading everything into memory.

Revise: groupingBy, partitioningBy, toMap and Joining.

S13. Top 10 Words in Support Tickets

Scenario: Product managers want the 10 most common words in this month's support tickets, ignoring case and punctuation.

Question: Implement it.

Show a strong answer
Java
Map<String, Long> freq = tickets.stream()
        .flatMap(t -> Arrays.stream(t.toLowerCase().split("\\W+")))
        .filter(w -> !w.isBlank())
        .collect(Collectors.groupingBy(w -> w, Collectors.counting()));
 
List<Map.Entry<String, Long>> top10 = freq.entrySet().stream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
        .limit(10)
        .toList();
  • Mention filtering stop words ("the", "is"), and that a PriorityQueue of size 10 avoids sorting every entry.

Revise: Stream flatMap in Java and groupingBy, partitioningBy, toMap and Joining.

S14. Order API That Sometimes Returns Null

Scenario: orderService.findOrders(customerId) returns null when a customer has no orders, and callers keep crashing.

Question: How should this API be designed?

Show a strong answer
  • Return an empty list (List.of()), never null, for "no results". Callers can loop safely.
  • For a single result that may be missing, return Optional<Order>.
  • For invalid input (an unknown customer ID), throw a clear exception rather than return empty, so errors aren't hidden.
  • Prevent: document the contract, and add tests for the "no orders" case.

Revise: Null Handling Strategies in Java.

S15. Two Customers Buy the Last Item

Scenario: Two users click "Buy" on the last item at the same moment. Both orders succeed, and stock becomes −1.

Question: Fix the race condition.

Show a strong answer
  • Cause: a check-then-act race: both threads read stock == 1, then both decrement.
  • In the database (the usual real fix): make the update atomic and conditional, then check the row count:
Java
// UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0
int updated = ps.executeUpdate();
if (updated == 0) throw new OutOfStockException(productId);
  • In memory (a single instance): use a CAS loop on an AtomicInteger:
Java
int current;
do {
    current = stock.get();
    if (current == 0) return false;
} while (!stock.compareAndSet(current, current - 1));
return true;
  • Mention optimistic locking with a version column, and that synchronized doesn't help when several app instances run.

Revise: Race Conditions and Thread Safety and Compare-And-Swap (CAS) in Java.

S16. Payment Gateway Is Slow

Scenario: Checkout calls an external payment-status API that sometimes takes 30 seconds, which blocks users.

Question: How do you call it safely?

Show a strong answer
Java
HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(2))
        .build();
HttpRequest request = HttpRequest.newBuilder(URI.create(statusUrl))
        .timeout(Duration.ofSeconds(3))
        .GET()
        .build();
 
CompletableFuture<String> status = client
        .sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .exceptionally(ex -> "PENDING");   // fallback: check again later
  • Always set timeouts. Retry only safe operations, with exponential backoff and a limit. Use a circuit breaker so a failing gateway isn't hammered. Show users a "processing" state instead of making them wait.

Revise: HttpClient in Java and CompletableFuture in Java.

S17. Sending Emails Slows Down Orders

Scenario: The order API sends a confirmation email in the same request, and email delays make checkout slow.

Question: How do you decouple it?

Show a strong answer
  • Send the email asynchronously on a bounded thread pool, so the order completes immediately:
Java
ExecutorService mailPool = new ThreadPoolExecutor(
        4, 4, 0L, TimeUnit.MILLISECONDS,
        new ArrayBlockingQueue<>(1_000),
        new ThreadPoolExecutor.CallerRunsPolicy());
mailPool.submit(() -> mailer.sendConfirmation(order));
  • Bounded queue + rejection policy = no unbounded memory growth. Shut the pool down cleanly on exit.
  • Real-world upgrade: in-memory tasks are lost on a crash, so for reliability write an "email to send" record in the same DB transaction as the order (the outbox pattern), or publish to a message queue.

Revise: Types of Thread Pools in Java.

Scenario: A security scan finds that the product search builds SQL as "... WHERE name LIKE '%" + term + "%'".

Question: Fix it.

Show a strong answer
Java
String sql = "SELECT id, name, price FROM product WHERE name LIKE ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, "%" + term + "%");
    try (ResultSet rs = ps.executeQuery()) { /* map rows */ }
}
  • Parameters are sent separately from the SQL text, so input can't change the query. Also: limit result sizes, give the DB user the least privileges it needs, and check other queries for the same pattern.

Revise: Statement vs PreparedStatement (SQL Injection).

S19. Bulk Import Takes 20 Minutes

Scenario: Importing 100,000 customers inserts one row at a time with auto-commit on, and takes 20 minutes.

Question: Speed it up.

Show a strong answer
Java
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(
        "INSERT INTO customer (id, name) VALUES (?, ?)")) {
    int count = 0;
    for (Customer c : customers) {
        ps.setLong(1, c.id());
        ps.setString(2, c.name());
        ps.addBatch();
        if (++count % 1_000 == 0) {
            ps.executeBatch();
            conn.commit();
        }
    }
    ps.executeBatch();
    conn.commit();
}
  • Batching cuts network round trips, and committing in chunks avoids one huge transaction. Mention driver settings (MySQL's rewriteBatchedStatements=true), handling a failed batch, and the database's own bulk-load tools for very large files.

Revise: Batch Processing in JDBC.

S20. Product Cache That Ate the Heap

Scenario: A product cache is a static HashMap that only ever grows. After a week, the service runs out of memory.

Question: Design a better cache.

Show a strong answer
  • An unbounded static map is a memory leak. Bound it and evict old entries, for example with an LRU cache:
Java
class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxEntries;
    LruCache(int maxEntries) {
        super(16, 0.75f, true);          // access order
        this.maxEntries = maxEntries;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxEntries;
    }
}
  • It isn't thread-safe: wrap it with Collections.synchronizedMap, or in production use a proven library such as Caffeine (size limits, expiry, statistics). Also decide how stale data is invalidated when prices change.

Revise: LinkedHashMap in Java and Memory Leaks in Java.

Hard Scenarios (S21 to S30)

Production incidents and system design: memory, deadlocks, CPU, load, latency and payments.

S21. Memory Keeps Growing Until the Service Crashes

Scenario: A service's memory grows slowly and it crashes with OutOfMemoryError: Java heap space every few days. Restarting "fixes" it.

Question: How do you investigate and fix it?

Show a strong answer
  1. Capture evidence: run with -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps, or take a dump from the live process (jcmd <pid> GC.heap_dump /tmp/heap.hprof). Turn on GC logs.
  2. Analyze: open the dump in Eclipse MAT or VisualVM, look at the dominator tree and the biggest retained sizes, and follow the path to GC roots.
  3. Typical causes: static collections or caches with no limit; listeners never removed; ThreadLocals in pools; unclosed resources; sessions kept forever.
  4. Fix and prove it: remove the reference, bound the cache, and confirm with a load test that the heap stays flat after GC.
  • Prevent: dashboards for heap after GC; alerts on a growing trend.

Revise: Memory Leaks in Java and Heap Dumps and Thread Dumps.

S22. Every Request Hangs

Scenario: A transfer service suddenly stops responding; CPU is near 0%. Transfers lock two accounts with synchronized blocks.

Question: What happened, and how do you fix it for good?

Show a strong answer
  • Diagnose: take a thread dump (jstack <pid> or jcmd <pid> Thread.print). It reports "Found one Java-level deadlock": thread 1 holds account A and waits for B, while thread 2 holds B and waits for A.
  • Fix: always lock in a global order, for example by account ID:
Java
Account first = a.id() < b.id() ? a : b;
Account second = first == a ? b : a;
synchronized (first) {
    synchronized (second) {
        transfer(a, b, amount);
    }
}
  • Alternatives: ReentrantLock.tryLock with a timeout and retry, or do the transfer in a single database transaction.
  • Prevent: a stress test that runs opposite transfers concurrently.

Revise: Deadlock in Java.

S23. CPU Stuck at 100%

Scenario: After a release, one service instance sits at 100% CPU and response times are terrible.

Question: Walk through finding the cause.

Show a strong answer
  1. top -H -p <pid> to find the hottest threads; convert their thread IDs to hex.
  2. Take 3 thread dumps a few seconds apart (jcmd <pid> Thread.print); match the nid=0x… values and look for the same stack each time.
  3. Check GC logs: constant GC (a nearly full heap) also shows up as high CPU.
  4. Common causes: an infinite loop, a regex with catastrophic backtracking on user input, a busy-wait loop, or a HashMap corrupted by unsynchronized concurrent writes.
  5. Confirm with Java Flight Recorder (jcmd <pid> JFR.start duration=60s filename=cpu.jfr), fix, and add a regression test.

Revise: Heap Dumps and Thread Dumps and JVM Monitoring and Troubleshooting Tools.

S24. Rate-Limit the Public API

Scenario: A public API must allow at most 100 requests per minute per user; extra requests get HTTP 429.

Question: Implement it in Java for one server instance, and explain what changes with several instances.

Show a strong answer

A token bucket per user allows short bursts and refills smoothly:

Java
class TokenBucket {
    private final long capacity;
    private final double refillPerNano;
    private double tokens;
    private long lastRefill = System.nanoTime();
 
    TokenBucket(long capacity, Duration period) {
        this.capacity = capacity;
        this.tokens = capacity;
        this.refillPerNano = (double) capacity / period.toNanos();
    }
 
    synchronized boolean tryAcquire() {
        long now = System.nanoTime();
        double refill = (now - lastRefill) * refillPerNano;
        tokens = Math.min(capacity, tokens + refill);
        lastRefill = now;
        if (tokens < 1) return false;
        tokens -= 1;
        return true;
    }
}
 
Map<String, TokenBucket> buckets = new ConcurrentHashMap<>();
boolean allowed = buckets
        .computeIfAbsent(userId,
                id -> new TokenBucket(100, Duration.ofMinutes(1)))
        .tryAcquire();
  • computeIfAbsent creates exactly one bucket per user; locking is per user, not global. Remove idle buckets so the map doesn't grow forever.
  • Several instances: in-memory limits are per instance, so move the counters to a shared store (for example Redis with atomic scripts), or enforce the limit at the API gateway.

Revise: Concurrent Collections in Java.

S25. 10,000 Concurrent Calls with a 200-Thread Pool

Scenario: A service calls slow downstream APIs. With a fixed pool of 200 platform threads, requests queue up under load. The team wants to handle 10,000 concurrent requests.

Question: How do virtual threads help, and what must you protect?

Show a strong answer
  • The work is I/O-bound: threads mostly wait. Virtual threads make waiting cheap, so use one per request:
Java
Semaphore dbPermits = new Semaphore(50);   // match the database pool size
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request r : requests) {
        executor.submit(() -> {
            dbPermits.acquire();
            try {
                return handle(r);
            } finally {
                dbPermits.release();
            }
        });
    }
}
  • Protect limited resources: the database still has, say, 50 connections; a Semaphore stops 10,000 threads from overwhelming it.
  • Check: no pooling of virtual threads; avoid large ThreadLocal caches; look for pinning in native calls (JFR's jdk.VirtualThreadPinned event); CPU-heavy work still needs a normal pool.

Revise: Virtual Thread Best Practices.

S26. Dashboard from Three Slow Services

Scenario: A customer dashboard needs data from profile, orders and loyalty services. Called one after another they take 3+ seconds. The page must respond within 2 seconds, showing partial data if a service is slow.

Question: Design the Java code.

Show a strong answer

Call them in parallel, with a timeout and default for each:

Java
CompletableFuture<Profile> profile = CompletableFuture
        .supplyAsync(() -> profileClient.get(id), ioPool)
        .completeOnTimeout(Profile.EMPTY, 2, TimeUnit.SECONDS)
        .exceptionally(ex -> Profile.EMPTY);
CompletableFuture<List<Order>> orders = CompletableFuture
        .supplyAsync(() -> orderClient.recent(id), ioPool)
        .completeOnTimeout(List.of(), 2, TimeUnit.SECONDS)
        .exceptionally(ex -> List.of());
CompletableFuture<Integer> points = CompletableFuture
        .supplyAsync(() -> loyaltyClient.points(id), ioPool)
        .completeOnTimeout(0, 2, TimeUnit.SECONDS)
        .exceptionally(ex -> 0);
 
CompletableFuture.allOf(profile, orders, points).join();
return new Dashboard(profile.join(), orders.join(), points.join());
  • Use a dedicated I/O executor (or virtual threads), not the common pool. completeOnTimeout doesn't cancel the underlying HTTP call, so also set timeouts on the clients. Log which parts fell back.
  • Modern alternative: structured concurrency (preview in Java 25) cancels the remaining subtasks automatically when the scope ends.

Revise: CompletableFuture in Java and Structured Concurrency in Java.

S27. Latency Spikes from GC Pauses

Scenario: A trading API has p99 latency spikes of 800 ms. GC logs show long pauses.

Question: How do you approach tuning?

Show a strong answer
  1. Measure: turn on GC logging (-Xlog:gc*:file=gc.log:time,uptime) and correlate pauses with the spikes. Check heap occupancy after GC, the promotion rate and the allocation rate.
  2. Reduce garbage first: profile allocations (JFR) and remove waste such as boxing in hot loops, large temporary objects, or excessive logging.
  3. Size the heap correctly: set -Xms equal to -Xmx to avoid resizing; make sure the container memory limit leaves headroom.
  4. Choose the collector: tune G1 with -XX:MaxGCPauseMillis, or switch to ZGC (-XX:+UseZGC) for sub-millisecond pauses at some throughput cost.
  5. Verify with a load test, and change one thing at a time.

Revise: Reading GC Logs in Java and Garbage Collectors in Java.

S28. Orders Jumping to Impossible States

Scenario: Bugs let orders move from SHIPPED back to PENDING, or from CANCELED to DELIVERED.

Question: How do you make illegal state changes impossible to write?

Show a strong answer

Put the rules in one place, the enum itself, and check them on every change:

Java
enum OrderStatus {
    PENDING, PAID, SHIPPED, DELIVERED, CANCELED;
 
    boolean canMoveTo(OrderStatus next) {
        return switch (this) {
            case PENDING -> next == PAID || next == CANCELED;
            case PAID -> next == SHIPPED || next == CANCELED;
            case SHIPPED -> next == DELIVERED;
            case DELIVERED, CANCELED -> false;
        };
    }
}
  • The Order class exposes moveTo(next), which throws IllegalStateException for illegal moves; there is no public setter. The exhaustive switch makes the compiler flag any new status that isn't handled.
  • Also: store a status history for auditing, and guard against two concurrent updates with optimistic locking.

Revise: Enums in Java and Exhaustive Switch with Sealed Types.

S29. Loading Plugins at Runtime

Scenario: A reporting product lets customers drop extra report types as JAR files into a plugins/ folder, without rebuilding the application.

Question: How do you load them in Java?

Show a strong answer

Define a plugin interface, and load implementations with a class loader and ServiceLoader:

Java
URL[] urls = { Path.of("plugins/sales-report.jar").toUri().toURL() };
ClassLoader parent = App.class.getClassLoader();
try (URLClassLoader loader = new URLClassLoader(urls, parent)) {
    ServiceLoader.load(ReportPlugin.class, loader)
            .forEach(plugin -> plugin.generate(data));
}
  • Each plugin JAR lists its class in META-INF/services/<interface name>.
  • Discuss: a separate class loader per plugin gives isolation and lets a plugin be unloaded (once nothing references its loader); version conflicts between plugins' dependencies; security, since plugins run with full permissions, so only load trusted JARs.

Revise: Class Loaders in Java.

S30. Customers Charged Twice

Scenario: A mobile app retries a payment request after a network timeout. Sometimes the first request had actually succeeded, so the customer is charged twice.

Question: Design idempotent payment processing.

Show a strong answer
  • The client sends a unique idempotency key (a UUID) with each logical payment and reuses it on retries.
  • The server processes each key once and returns the stored result for repeats.
  • Across instances and restarts (required): a table with a unique constraint on the key, written in the same transaction as the payment record. A duplicate insert fails, and you return the saved result.
  • Within one instance, also collapse concurrent duplicates:
Java
ConcurrentHashMap<String, CompletableFuture<Receipt>> inFlight =
        new ConcurrentHashMap<>();
 
Receipt pay(String idempotencyKey, PaymentRequest request) {
    return inFlight.computeIfAbsent(idempotencyKey,
                    key -> CompletableFuture.supplyAsync(
                            () -> gateway.charge(request, key)))
            .join();
}
  • Pass the key on to the payment gateway too (most support one), give stored keys an expiry, and test by sending the same request twice concurrently.

Revise: Transaction Management in JDBC and Concurrent Collections in Java.

What Next

  • Practice the shorter questions in 100 Core Java Interview Questions and Answers, filtered by level.
  • Rehearse under time pressure with the Java Mock Interview.
  • Build something real. Many of these scenarios, such as safe money handling, idempotent requests and bounded caches, make good features for a project you can show in an interview.

Was this tutorial helpful?