Runnable Java 21 and Spring Boot interview preparation labs covering Core Java, collections, multithreading, JVM internals, Spring Boot, JPA/Hibernate, SQL, REST APIs, system design, and production tradeoffs.
This repository is designed for Java backend developers preparing for interviews, strengthening production skills, or teaching enterprise engineering. Every topic has executable code, tests, failure scenarios, and interview-level tradeoffs. It is useful for beginner, experienced, senior, and lead Java developer preparation, including Spring Boot backend roles.
- Why this lab?
- Quick start
- Java and Spring Boot interview roadmap
- Learning path
- Interview question index
- Repository map
- Interview preparation
- Testing strategy
- Contributing
Use the roadmap below to prepare for Java backend interviews at service companies, product companies, and startups. Study the explanation, run the example, break the implementation, and use the test to verify the behavior.
- OOP, abstraction, inheritance, polymorphism, and composition.
Stringimmutability, string pool,equals(), andhashCode().- Collections, generics,
Optional, exceptions, and Java Streams. - Multithreading,
synchronized,volatile, executors, andCompletableFuture. - JVM memory, garbage collection, class loading, and diagnostic tools.
- Dependency injection, bean lifecycle,
@Configuration, and@Bean. - Conditional beans, profiles, configuration boundaries, and testing with doubles.
@Transactional, rollback, self-invocation, caching, and proxy behavior.- REST resources, validation, pagination, error responses, and idempotency.
- JPA/Hibernate fetch plans, N+1 queries, locking, batching, and transactions.
- SQL indexes, query plans, isolation, deadlocks, and keyset pagination.
- Retries, timeouts, circuit breakers, backpressure, and graceful shutdown.
- Idempotency, outbox delivery, event redelivery, reconciliation, and observability.
- API scalability, multi-tenant boundaries, consistency, and failure isolation.
- How to explain complexity, tradeoffs, testing strategy, and operational risk.
The examples use generic enterprise scenarios rather than leaked or company-specific interview questions. For India-focused preparation, combine this roadmap with the coding, SQL, Spring Boot, and system-design rounds used for Java backend roles.
Enterprise engineering is more than making an endpoint work. It is knowing:
- Which lifecycle hook or abstraction owns a behavior.
- How a query behaves with millions of rows.
- Where transactions, caches, and proxies stop working.
- How concurrency, failure, retries, and consistency change a design.
- What really happens inside frequently discussed Java APIs, not just how to call them.
- How to explain tradeoffs clearly in a senior or lead interview.
This is an interview-focused, practical Java and Spring lab. It includes runnable code, tests, traces, and failure scenarios for:
- Java fundamentals:
Stringimmutability, string pooling,==versusequals,StringBuilder, andStringBuffer. - Collections and concurrency:
ConcurrentHashMaphashing, buckets, collision lookup,get,putIfAbsent, and atomicmerge. - Iterator consistency: fail-fast
ArrayListbehavior,ConcurrentModificationException, snapshot iteration, and fail-safe-styleCopyOnWriteArrayListusage. - JVM internals: stack, heap, metaspace, code cache, native memory, Eden, Survivor S0/S1, Old generation, promotion, and GC reachability.
- Enterprise Java and Spring: bean lifecycle, dependency injection, transactions, caching, JPA, SQL, streams, API design, concurrency, and design patterns.
The examples intentionally include both the good path and the trap. Every topic asks the practical interview question: when should this be used, what does it cost, and what breaks under production load?
Prerequisites:
- Java 21 or newer
- Maven 3.9 or newer
Run the tests:
mvn clean testStart the application:
mvn spring-boot:runThe application starts an HTTP server on http://localhost:8080, seeds 10,000 pagination records in an in-memory H2 database, and prints the feature demonstrations to the console.
Try the API:
curl 'http://localhost:8080/api/pagination/records/cursor?tenantId=tenant-a&limit=5'
curl 'http://localhost:8080/api/pagination/records/offset?tenantId=tenant-a&offset=100&limit=5'The cursor response contains nextCursor; pass it to retrieve the next page.
The application can run one learning topic instead of printing every demo. The default remains the complete lab:
mvn spring-boot:runList the available topics:
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=help"Run one topic and read its Observe, Break it, and Enterprise question
prompts before changing the nearby implementation:
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=concurrency"
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=jpa"
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=features"
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=jvm"
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=pagination"
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=rest"Available topic names are lifecycle, features, concurrency, strings,
jvm, jpa, sql, streams, design, pagination, and rest. The transactions
name is covered by the features runner, so use --playground=features for
that example.
Use this loop for each topic:
- Read the printed
Observeprompt and the linked implementation. - Run the focused test class with
mvn -Dtest=<TestClass> test. - Change the line described by
Break itand rerun the test. - Restore the code and answer the enterprise question in the context of a real service.
The first executable break/fix exercises are in the concurrency module:
- ImmutableUserProfile.java is the corrected immutable value object.
- BadImmutableUserProfile.java deliberately aliases the caller's mutable list.
- SingletonBreakageExamples.java demonstrates reflection bypassing a private Singleton constructor.
- SerializableSingletonWithoutReadResolve.java demonstrates deserialization creating a second instance.
- EnumSingleton.java provides the robust enum alternative.
Run those exercises with:
mvn -Dtest=ConcurrencyExamplesTests testThe JVM topic prints live memory-pool and reachability observations. The pagination topic runs offset and cursor requests in-process and then you can continue with the HTTP examples from the API section above.
Additional enterprise failure labs are executable in the normal test suite:
BeanLifecycleDemoApplicationTests#selfInvocationBypassesTransactionalProxyshows why an internal call does not cross a Spring transaction proxy.PaginationApiTests#cursorCannotBeReplayedAcrossTenantsshows tenant-bound cursor validation.SqlQueryExamplesTests#productionQueriesHaveDeterministicBoundschecks that latest-row and keyset queries are ordered and bounded.StreamCodingExamplesTests#streamPlaygroundMakesTieBreakerFailureVisiblecompares an implicit stream tie with an explicit business rule.
The concurrency playground now covers the questions to ask before approving asynchronous production code:
- Executor ownership: who creates, names, sizes, and shuts down the pool?
- Queue policy: is work bounded, and what happens when the queue is full?
- Failure handling: where does a failed
FutureorCompletableFuturebecome visible? - Cancellation: does interruption stop the task, and is cleanup idempotent?
- Composition: are independent calls combined with
allOfinstead of blocking one by one? - Timeouts and fallback: is a downstream timeout bounded, observable, and paired with a safe fallback?
- Context: how are tenant, trace, security, and logging contexts propagated across threads?
- Load behavior: what happens under overload, deploy shutdown, dependency slowness, and partial failure?
Run the focused exercises with:
mvn -Dtest=ConcurrencyExamplesTests testThe implementation is in AsyncCoordinationPlayground.java.
Follow the sections in this order:
- How does the Spring bean lifecycle work? See construction, dependency injection, initialization callbacks, post-processors, ready events, proxies, and destruction.
- How should Spring choose implementations? Practice constructor injection, conditional beans, replacement with test doubles, and configuration boundaries.
- How do transactions and caches work through Spring proxies? Observe commit, rollback, cache hits, and the self-invocation trap.
- What is the difference between
String,StringBuilder, andStringBuffer? Compare immutability, string pooling,==,equals, local mutation, and synchronized legacy builders. - How do concurrent Java collections work internally? Trace
ConcurrentHashMaphashing, buckets, collisions,get,putIfAbsent, atomicmerge, andCopyOnWriteArrayListsnapshot writes. - What is the difference between fail-fast and fail-safe-style iteration? Reproduce
ConcurrentModificationException, multithreaded structural changes, stable snapshots, and collection-selection tradeoffs. - How is JVM memory divided and how does garbage collection work? Connect stack references, heap objects, metaspace, native memory, Eden, S0/S1, Old generation, promotion, memory pools, and reachability.
- How do JPA and SQL behave at scale? Measure N+1 queries, fetch plans, joins, window functions, indexes, transactions, and offset versus keyset pagination.
- How should Java Streams solve common coding problems? Practice grouping, flattening, ranking, duplicate handling, ordering,
Optional, primitive aggregation, and parallelism tradeoffs. - How do SOLID principles and design patterns work in an enterprise service? Apply Strategy, Factory, Adapter, Decorator, Observer, Builder, and Facade to payments.
- How should a production API handle pagination and errors? Compare offset and cursor pagination, validate input, model sealed error categories, and test continuation and tampering.
Use these questions as a practical study checklist. Each topic has executable Java or Spring code, tests that state the expected behavior, and documentation explaining the production tradeoff.
- Why is
Stringimmutable, and how does string pooling affect object identity? - Why can
==appear to work for string literals but fail for separately created strings? - When should I use
StringBuilderinstead of repeated string concatenation? - When is
StringBufferappropriate, and why does synchronized mutation not automatically make a workflow thread-safe?
- How does
ConcurrentHashMapcalculate a hash and select a bucket? - How does
ConcurrentHashMap.get()find a value and handle hash collisions? - Why can concurrent readers proceed without one global map lock?
- Why are
putIfAbsent,compute, andmergedifferent from separategetandputcalls? - How does
ConcurrentHashMap.merge()prevent lost updates for a single key under contention? - When should I use
ConcurrentHashMapinstead ofHashMapor synchronized access? - How does
CopyOnWriteArrayListlet an iterator read a stable snapshot during a write? - Why is
CopyOnWriteArrayListuseful for listener registries but expensive for write-heavy workloads? - What does a fail-fast iterator detect, and why is
ConcurrentModificationExceptionnot a synchronization mechanism? - When should I choose synchronized iteration, a snapshot, a concurrent collection, or message passing?
- What is stored in a Java thread stack, and how is a reference different from the heap object it points to?
- What lives in the heap, metaspace, code cache, and native memory?
- What happens to a new object in Eden during a young or minor collection?
- What are Survivor S0 and S1, and why do they alternate roles?
- When is an object promoted from the young generation to the Old or Tenured generation?
- What is the difference between a minor collection and an old-generation or major collection?
- How do GC roots determine whether an object is reachable or eligible for collection?
- Why does
System.gc()not guarantee immediate garbage collection? - How do G1 regions differ from the classic Eden/Survivor/Old generation diagram?
- How can MXBeans, GC logs, JFR, heap dumps, and allocation profiles help diagnose memory problems?
- In what order does Spring construct, initialize, proxy, and destroy a bean?
- When should I use constructor injection,
@PostConstruct,ApplicationReadyEvent, or a shutdown callback? - How do
@ConditionalOnProperty,@Primary,@Qualifier, and@ConditionalOnMissingBeanaffect implementation selection? - What problem do
@Configurationand@Beansolve, and when should I choose them over@Component? - How can I register a third-party client or JDK type as a Spring bean and inject it into another bean?
- Why do
@Transactionaland@Cacheablefail when called through self-invocation? - How should a service handle rollback, retries, idempotency, outbox events, and external provider failures?
- How do Strategy, Factory, Adapter, Decorator, Observer, Builder, and Facade solve different change pressures?
- How do I prove an N+1 query problem and choose between
EntityGraph,JOIN FETCH, DTO projection, and JDBC? - How do lazy loading, persistence context state, dirty checking, optimistic locking, and batch processing interact?
- How do
GROUP BY,HAVING, window functions, anti-joins, duplicate detection, and running totals work? - When should I use offset pagination versus cursor or keyset pagination?
- How do I make a cursor deterministic, tenant-safe, bounded, and tamper-resistant?
- How do Java Streams handle grouping, flattening, duplicates, ordering,
Optional, and primitive aggregation? - When is a loop clearer or safer than a stream, and when is
parallelStream()a bad performance assumption? - How should a Java 21 API model validation errors with records, sealed types, pattern matching, and controller advice?
| Topic | Code | What to run or inspect |
|---|---|---|
| Bean lifecycle | lifecycle package | LifecycleBean, aware callbacks, post-processors |
| DI, conditions, transactions, cache | features package | OrderService, ShippingConfiguration, ProductCatalog |
| Singleton and concurrency | concurrency package | singleton publication, immutable values, collections |
| JVM memory and GC | JVM package | memory areas, live MXBean measurements, reachability |
| JPA and Hibernate | jpa package | N+1, @EntityGraph, fetch join, query counts |
| SQL interview queries | sql package | grouping, windows, keyset query, anti-join |
| Java 8+ Streams | streams package | coding exercises and edge cases |
| Strings and iterators | StringExamples.java, IteratorExamples.java | identity versus equality, builders, fail-fast and snapshot iteration |
| SOLID and patterns | design package | enterprise payment authorization scenario |
| Pagination and API errors | pagination package | HTTP endpoints, cursors, sealed problems |
| REST API design and idempotency | rest package | Path/query parameters, retries, idempotency, payload limits |
| Tests | test package | executable specifications for every module |
Start with BeanLifecycleDemoApplication.java and LifecycleBean.java.
The demonstrated startup sequence is:
Constructor
-> dependency injection and Aware callbacks
-> BeanPostProcessor before initialization
-> @PostConstruct
-> InitializingBean.afterPropertiesSet
-> configured custom init method
-> BeanPostProcessor after initialization
-> SmartInitializingSingleton
-> ApplicationRunner
-> ApplicationReadyEvent
Graceful shutdown invokes @PreDestroy, DisposableBean.destroy(), and the configured destroy method. The relative order of individual Aware callbacks is controlled by Spring and is not an application contract.
Use constructor injection for required dependencies, @PostConstruct for short local setup, @PreDestroy for owned resources, and ApplicationReadyEvent for bounded application-level startup work. Avoid database calls, network calls, long tasks, or cross-bean assumptions in constructors and local initialization callbacks.
Useful breakpoints:
LifecycleBean()LifecycleBean.setApplicationContext(...)LifecycleBean.postConstruct()LifecycleLoggingPostProcessorStartupObserver.afterSingletonsInstantiated()ApplicationStartupPhases.run()ApplicationStartupPhases.onApplicationReady()
The features package demonstrates programming to interfaces:
OrderServicereceivesOrderRepositoryandShippingProviderthrough constructor injection.ShippingConfigurationselectsFastShippingProviderwith@ConditionalOnPropertyand suppliesDefaultShippingProviderwith@ConditionalOnMissingBean.OrderServicedemonstrates@Transactionalcommit and rollback using H2.ProductCatalogdemonstrates@Cacheableand cache hits.ConfigurationBeanDemoConfigurationdemonstrates@Configurationas a configuration boundary and@Beanas an explicit factory for a JDK/infrastructure type.
@Configurationtells Spring that a class contains bean definitions and configuration rules. Spring discovers it during component scanning and uses it to build the application context.@Beantells Spring to call a method, take the returned object, and manage it as a bean: dependency injection, lifecycle, scopes, conditions, and proxy integration can apply.- Use
@Componentwhen you own a class and it is naturally a scanned application component. Use@Beanwhen you need to construct a third-party class, adapt an SDK, choose an implementation, configure constructor arguments, or express a factory decision. - Prefer method parameters for dependencies between
@Beanmethods, as shown byconfiguredGreetingService(Clock demoClock). This makes the dependency explicit and testable. @Configurationis a boundary for application wiring, not a replacement for business services. Keep business behavior in normal services and keep object construction and environment choices in configuration.
Important traps:
- Two candidates without
@Primary,@Qualifier, or a condition causeNoUniqueBeanDefinitionException. @Transactionaland@Cacheableare proxy-based; self-invocation bypasses the proxy.- Unchecked exceptions roll back by default; checked exceptions need an explicit policy.
- A database transaction does not include an HTTP call or message broker.
- Conditional beans are startup decisions, not live feature flags.
- Local caches are per instance; distributed consistency needs a shared cache and invalidation design.
The concurrency package compares:
- Initialization-on-demand holder singleton and
volatiledouble-checked locking. - Immutable values with defensive copies.
HashMapfor local state.LinkedHashMapfor deterministic encounter order.ConcurrentHashMapwith atomicmergeupdates.CopyOnWriteArrayListfor many reads and rare writes.- Bounded
BlockingQueuefor producer-consumer backpressure.
The enterprise rule is to decide the scope first: request, thread, JVM, application context, or cluster. Prefer immutable data and message passing before locks. A JVM singleton is not a cluster singleton, and a thread-safe collection does not make a multi-step business workflow atomic.
The runnable traces in ConcurrentHashMapInternalsDemo.java and CopyOnWriteArrayListInternalsDemo.java explain the important path without reproducing private JDK source:
ConcurrentHashMap.get(key): calculatehashCode(), spread high bits, choose a bucket with(capacity - 1) & hash, read without locking, then compare hash andequals()while checking collisions.ConcurrentHashMap.putIfAbsent: use the same hash and bucket path; an empty bucket can be published atomically, while a competing update for the same key is coordinated rather than silently overwriting data. Different keys can make progress independently.ConcurrentHashMap.merge: the read, value computation, and publication form one atomic update for that key. This is whymergeworks for concurrent counters while separategetplusputcan lose updates. The mapping function should be short and side-effect free.CopyOnWriteArrayListreads: an iterator captures the current array reference, so iteration needs no lock and remains stable while another thread writes.CopyOnWriteArrayListwrites:addorremovecopies the array, changes the copy, and publishes it as the new current array. Existing iterators keep the old snapshot; new iterators see the new array.
The traces are deliberately a mental model, not a promise about private implementation details such as the exact bin-lock or treeification code in a particular JDK version. Use ConcurrentHashMap for shared key/value state with atomic per-key operations; use CopyOnWriteArrayList for many reads and rare writes such as listener lists. Neither collection makes an arbitrary multi-step business workflow atomic.
The JVM package provides a practical memory model without pretending that every JVM or garbage collector has the same internal layout.
The mental model is:
- Thread stack: method frames, parameters, local primitives, and references. A reference is not the object itself.
- Heap: objects and arrays. The garbage collector reclaims objects that are no longer reachable from GC roots such as live thread stacks, static fields, and JNI references.
- Metaspace: class metadata in native memory. Loading many classes can grow it even when the Java heap looks healthy.
- Code cache: JIT-compiled machine code, also outside the Java heap.
- Other native memory: thread stacks, direct byte buffers, JNI allocations, and JVM bookkeeping.
Run JvmMemoryAndGcDemo.memorySnapshot() to inspect live heap/non-heap usage, memory pools, and collector counts through standard MXBeans. Values are measurements at one moment, not fixed limits; max can be unavailable and is represented by -1.
The generational GC trace covers the classic interview model: new objects begin in Eden; a young/minor collection copies surviving objects through alternating S0 and S1 Survivor areas; objects that survive enough cycles may be promoted to Old/Tenured generation. generationalPools() maps live pool names to these concepts when the active collector exposes them. The label SURVIVOR_S0_OR_S1 is intentional: the same logical survivor pool may alternate roles, and the MXBean name does not always identify which side is currently active.
Do not apply the classic picture too literally to every collector. G1 divides the heap into regions and assigns regions to young or old roles; ZGC and Shenandoah use different designs. The code therefore keeps both the actual pool name and a conceptual classification, and reports OTHER_HEAP when a collector's names do not match the classic labels.
The GC example follows the important lifecycle: create an object, hold a strong reference, clear that reference, and observe that the object becomes eligible for collection. System.gc() is only a request. Collection timing, collector choice, generations, pauses, compaction, and promotion depend on the JVM, flags, allocation pressure, and runtime workload. The test therefore proves reachability, not a particular collection time.
For production diagnosis, use allocation profiles, GC logs, JFR, heap dumps, and container-aware memory limits. Do not use System.gc() as an application memory-management strategy.
The JPA module uses Department and Employee entities to make N+1 measurable:
Naive lazy access: 3 SQL statements for 2 departments
@EntityGraph: 1 SQL statement
JOIN FETCH: 1 SQL statement
The tests prove the counts with Hibernate statistics. The README and code cover:
- Lazy versus eager associations.
@EntityGraphversusJOIN FETCHversus DTO projection.LazyInitializationException.- Pagination with collection fetch joins.
MultipleBagFetchException.- Dirty checking, flush, merge, and persistence context states.
- Optimistic and pessimistic locking.
@Version, batch processing, and outbox design.- When JDBC or jOOQ is more appropriate than JPA.
The main lesson: reducing query count is not enough. Check returned rows, execution plans, memory, transaction duration, and connection-pool pressure.
SqlQueryExamples.java contains runnable queries for the existing H2 schema and templates for larger banking-style schemas:
GROUP BYandHAVING.- Latest row per customer with
ROW_NUMBER. - Top N per account.
- Keyset pagination.
- Duplicate detection.
- Running totals.
- Customers without orders using an anti-join.
- Transaction totals.
Lead-level SQL topics include null semantics, join multiplication, WHERE versus HAVING, UNION versus UNION ALL, composite indexes, execution plans, isolation, deadlocks, lost updates, SQL injection, and outbox consistency. The project favors bind parameters and deterministic ordering.
The Streams module uses Java 8-compatible APIs and covers:
- Word frequency and first non-repeated character.
- Duplicate detection and
flatMap. - Grouping, partitioning, downstream collectors, and joining.
- Top N, second/Nth highest, anagrams, and tie-breaking.
Optionalfor absent results.- Primitive aggregation with
mapToInt. - Null handling, duplicate keys, ordering, side effects, and exceptions.
- Why
parallelStream()is not a free performance switch.
For each stream problem, state empty-input behavior, duplicate semantics, ordering, complexity, memory cost, and whether parallel execution is safe. A clear loop is better than an opaque stream pipeline.
StringExamples.java makes two common interview distinctions executable:
==compares whether two references point to the same object;equalscompares string content. String interning can make==appear to work for literals, so production code should useequalsfor values.Stringis immutable.StringBuilderis mutable and preferred for local, single-threaded assembly;StringBufferprovides synchronized methods for legacy shared mutable use, but a lock does not automatically make a multi-step workflow correct.
IteratorExamples.java contrasts collection consistency models:
- A normal
ArrayListiterator is fail-fast on a best-effort basis. An unsynchronized structural change may result inConcurrentModificationException; this is a bug signal, not a thread-safety mechanism or a guaranteed outcome. CopyOnWriteArrayListiterators are snapshot-based. Readers see a stable snapshot without that exception, while writes copy the backing array, making it suitable for many reads and rare writes such as listener registries. It is not a universal replacement for synchronized coordination.
For lead-level designs, first decide whether the requirement is fail-fast detection, a stable snapshot, synchronized live iteration, or a concurrent algorithm with explicit atomic operations. Then select the collection and synchronization boundary to match that requirement.
The design package models payment authorization:
PaymentCommand -> EnterprisePaymentService
|-> FraudCheck port -> legacy API adapter
|-> PaymentGatewayFactory -> payment strategies
|-> audited gateway decorator
\-> payment-completed observers
SOLID examples:
- Single Responsibility: orchestration, fraud translation, payment, and audit have separate change reasons.
- Open/Closed: add a provider strategy without rewriting orchestration.
- Liskov Substitution: gateway implementations honor a truthful contract.
- Interface Segregation: small
FraudCheck,PaymentGateway, and listener ports. - Dependency Inversion: business policy depends on ports, not vendor SDKs.
Patterns demonstrated:
- Strategy for payment methods.
- Factory for validated provider selection.
- Adapter for a legacy fraud API.
- Decorator for audit behavior.
- Observer for completion reactions.
- Builder plus record for an immutable command.
- Facade/application service for one business operation.
Do not add patterns to increase class count. Add an abstraction when it localizes a likely change, isolates a failure boundary, improves testing, or clarifies ownership. For payments, discuss idempotency, provider timeout after acceptance, reconciliation, durable events, and sensitive-data logging.
The pagination package seeds 10,000 rows and exposes:
GET /api/pagination/records/offset?tenantId=tenant-a&offset=100&limit=20
GET /api/pagination/records/cursor?tenantId=tenant-a&limit=20
GET /api/pagination/records/cursor?tenantId=tenant-a&cursor=<nextCursor>&limit=20
| Requirement | Better default | Reason |
|---|---|---|
| Small dataset or page-number UI | Offset | Simple and supports direct page jumps |
| Very large sequential feed | Cursor/keyset | Seeks from an indexed position instead of walking deep offsets |
| Infinite scroll/mobile API | Cursor/keyset | Stable forward traversal and bounded work |
| Billion-row export | Async job plus bounded chunks | Do not hold an HTTP request or memory for the whole export |
Cursor pagination still needs a deterministic unique ordering, such as (created_at, id), tenant/filter binding, cursor expiry, and signed or encrypted opaque state in production. It does not automatically provide a snapshot under concurrent writes.
- Records model immutable page items, responses, and error values.
- A sealed
ApiProblemhierarchy constrains known error categories. - Pattern matching in
ApiErrorResponseprovides an exhaustive mapping. @RestControllerAdvicekeeps exception policy out of controllers.- Unexpected errors return a safe generic message while details remain in server logs.
The API validates tenant, cursor, and page size. It caps limit at 100 and uses (tenant_id, id) for the access path. The tests cover cursor continuation, offset pages, invalid cursors, and invalid limits.
Run the REST playground to print the failure scenarios and lead-level design questions:
mvn spring-boot:run -Dspring-boot.run.arguments="--playground=rest"The same topic exposes runnable Spring MVC examples under /playground/rest:
curl 'http://localhost:8080/playground/rest/users/getUser?id=101'
curl 'http://localhost:8080/playground/rest/users/101'
curl 'http://localhost:8080/playground/rest/users?role=admin&status=active&size=20'
curl -X POST 'http://localhost:8080/playground/rest/orders/bad-create' \
-H 'Content-Type: application/json' \
-d '{"customerId":42,"items":[101,102]}'
curl -X POST 'http://localhost:8080/playground/rest/orders/good-create' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: checkout-42' \
-d '{"customerId":42,"items":[101,102]}'Call the good create command twice with the same Idempotency-Key and compare it with the bad create endpoint. Other endpoints demonstrate PATCH retry behavior, DELETE consistency, large query filters, and payload-size rejection. The implementation is organized under the rest package with separate controller, service, DTO, and repository layers.
| Lead question | Problem to observe | Bad code | Good code |
|---|---|---|---|
| Where does resource identity belong? | Action routes hide resource intent. | badGetUser -> readBadUser |
goodGetUser -> readGoodUser |
| What happens when POST is retried? | A create can produce duplicate orders. | badCreateOrder -> createBadOrder |
goodCreateOrder -> createGoodOrder |
| Is PATCH safe to retry? | Delta updates apply the same side effect repeatedly. | patchBadBalance |
patchGoodBalance |
| What should repeated DELETE do? | Retries can repeat cleanup or return inconsistent results. | deleteBad |
deleteGood |
| Where should request limits be enforced? | Arbitrary filters and large bodies pressure infrastructure and the database. | manyQueryParams and unrestricted request input |
largePayload with service-side size observation |
The REST study guide prints this same map when running --playground=rest, including the endpoint to call for each bad and good implementation.
The repository includes senior/lead themes commonly discussed in large enterprise and financial-services engineering interviews. They are not claimed to be leaked company-specific question lists.
Prepare to explain:
- Why
Stringis immutable, whenStringBuilderis preferable toStringBuffer, and whyequalsis correct for content comparison while==checks identity. - How
ConcurrentHashMapcalculates a bucket, reads without a global lock, handles collisions, and makesmergeorcomputeatomic for one key. - Why
getfollowed byputis not the same as an atomic map operation under contention. - Why
CopyOnWriteArrayListis effective for many reads and rare writes, and why copying on every write is expensive for write-heavy workloads. - What fail-fast iterators detect, why
ConcurrentModificationExceptionis best-effort rather than a synchronization mechanism, and when snapshot iteration is appropriate. - How stack references, heap objects, GC roots, Eden, Survivor S0/S1, promotion, Old generation, metaspace, and native memory fit together.
- Why
System.gc()is not a production memory-management strategy and how to investigate memory with GC logs, JFR, heap dumps, and allocation profiles. - How you prove an N+1 fix with query counts and plans.
- When cursor pagination beats offset and how to secure a cursor.
- How to prevent duplicate payments with idempotency and reconciliation.
- How isolation, optimistic locking, constraints, and retries work together.
- Why
@Transactionaland@Cacheablefail on self-invocation. - How an outbox makes database state and event publication reliable.
- How to process millions of rows without one huge transaction or persistence context.
- When to choose JDBC/jOOQ over JPA.
- How to test concurrency, provider failures, cursor tampering, and event redelivery.
- How to trade off simplicity, extensibility, observability, and operational risk.
The tests are executable learning notes:
mvn -q -Dtest=BeanLifecycleDemoApplicationTests test
mvn -q -Dtest=ConcurrencyExamplesTests test
mvn -q -Dtest=JvmMemoryExamplesTests test
mvn -q -Dtest=JpaExamplesTests test
mvn -q -Dtest=PaginationApiTests test
mvn -q -Dtest=StreamCodingExamplesTests test
mvn -q -Dtest=SqlQueryExamplesTests test
mvn -q -Dtest=DesignPrinciplesTests testRun all tests before opening a pull request:
mvn clean testsrc/main/java/com/example/lifecycle/
├── concurrency/ Singleton, immutable values, concurrent collections
├── design/ SOLID and enterprise design patterns
├── features/ DI, conditions, transactions, caching
├── jpa/ Entities, N+1, entity graphs, fetch joins
├── pagination/ HTTP pagination and global API errors
├── sql/ SQL query catalog and JDBC runner
└── streams/ Java 8+ Stream coding exercises
This project is intentionally easy to fork and extend. A useful contribution should include:
- A small runnable example.
- Comments explaining the decision and its failure mode.
- A focused test for the behavior, not only context loading.
- A README section with use case, tradeoffs, and when not to use it.
- No secrets, real customer data, or private interview questions.
Good next modules include resilience and retries, Testcontainers, messaging and outbox delivery, observability, security boundaries, rate limiting, and contract testing.
Please read CONTRIBUTING.md before opening a pull request. Use GitHub Discussions for preparation questions and study ideas, and use an issue template for a focused content request or bug report.
Java Spring Boot Interview Preparation Lab describes the repository's primary purpose while leaving room for more enterprise Java, backend, and system-design topics.
If this lab helps your preparation, fork it, adapt the examples, and share improvements through a pull request or issue. Keep examples runnable and explain the tradeoff behind every pattern.