Senior backend interviews are no longer limited to Java syntax or Spring Boot annotations. Interviewers increasingly expect developers to understand Java internals, Spring Boot behavior, database performance, distributed systems, Kafka, AWS architecture, resilience, observability, and production troubleshooting.
This guide covers 35 Java, Spring Boot, AWS and Microservices interview questions with practical answers. It moves from core Java and concurrency to Spring Boot, JPA, security, microservices, Kafka, AWS and a complete scalable e-commerce system design.
These questions are especially useful for Senior Java Developers, Senior Backend Developers, Spring Boot Developers, Microservices Developers and Software Architects preparing for technical interviews.
HashMap stores key-value entries in an internal bucket array. The key's hash is used to determine the bucket where the entry should be stored.
When multiple keys map to the same bucket, Java handles collisions using linked nodes and, under suitable conditions, can transform a long bucket chain into a balanced tree structure.
HashMap uses both hashCode() and equals() to locate the correct key. Its average lookup complexity is O(1), although collisions can affect performance.
When the number of entries crosses the resize threshold, the internal table grows and entries are redistributed.
Interview tip: If an object is used as a HashMap key, its equals() and hashCode() implementations must follow their contract.
| HashMap | ConcurrentHashMap |
|---|---|
| Not designed for concurrent modification | Designed for concurrent access |
| Allows null key and null values | Does not allow null keys or values |
| External synchronization may be required | Provides concurrency control internally |
ConcurrentHashMap is useful when multiple threads need to read and update shared map data without synchronizing the entire map around every operation.
It is commonly used for shared in-memory state where concurrent reads and updates are required.
Java provides threads, synchronization, locks, atomic classes, concurrent collections, executor frameworks and higher-level APIs such as CompletableFuture.
For server applications, I would normally avoid creating unmanaged threads for every request or task. ExecutorService and appropriately configured application-managed executors provide better control over concurrency.
Shared mutable state requires appropriate synchronization or atomic operations to prevent race conditions and establish safe visibility.
Modern Java applications can also use virtual threads for suitable high-concurrency, I/O-bound workloads, while CPU-bound work still requires careful consideration of available processors and workload characteristics.
For example, count++ is not made thread-safe merely by declaring count volatile because the increment consists of multiple steps.
If multiple operations need to happen as one atomic unit, synchronization or an appropriate locking strategy may be required.
A Stream represents a pipeline over a data source. Intermediate operations such as filter() and map() are generally lazy. Processing begins when a terminal operation such as collect(), count() or forEach() is invoked.
Source
|
v
Intermediate Operations
|
v
Terminal Operation
Streams can make data-processing code more expressive, but they are not automatically faster than traditional loops.
Parallel streams also require careful consideration of workload, shared state and execution behavior. Using parallelStream() does not automatically improve application performance.
I would first confirm the CPU spike and identify which process and threads are consuming CPU.
Common causes include tight loops, excessive serialization, inefficient algorithms, excessive logging, thread contention, garbage collection or unexpectedly high request volume.
The important point is to identify the actual CPU-consuming code before changing JVM or Kubernetes resource settings.
I would first distinguish a genuine memory leak from normal heap growth or a legitimate increase in workload.
I would examine heap usage and garbage-collection behavior. If memory continues growing after collections, I would capture a heap dump and analyze retained objects and GC roots.
Common causes include:
Heap analysis tools and Java Flight Recorder can help identify what is retaining memory.
Spring Boot evaluates the classpath, configuration properties and conditional rules and creates appropriate beans when the required conditions are satisfied.
Auto-configuration is conditional rather than blindly creating every possible bean.
For example, when database-related dependencies and configuration are present, Spring Boot can configure appropriate infrastructure automatically.
Developers can override auto-configuration by defining their own beans or changing configuration.
Interview point: Auto-configuration is one of the main reasons Spring Boot applications can be started with relatively little explicit configuration.
Spring's IoC container creates and manages beans and injects dependencies between them.
Constructor injection is generally preferred because dependencies are explicit and required dependencies can be enforced when the object is created.
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Spring discovers the bean and supplies it to the constructor when creating the OrderService.
Constructor injection also makes unit testing easier because dependencies can be passed directly into the class.
@Transactional is implemented through Spring's transaction infrastructure, commonly using proxies around the target bean.
When a transactional method is invoked through the appropriate proxy, Spring starts or joins a transaction according to the configured propagation behavior.
The transaction is committed on successful completion or rolled back according to the configured rollback rules when an applicable exception occurs.
The proxy model is important because direct self-invocation inside the same object can bypass the transactional proxy.
Caller
|
v
Spring Proxy
|
+---- Start / Join Transaction
|
v
Target Method
|
v
Commit / Rollback
Several situations can cause unexpected rollback behavior.
When investigating the issue, inspect the exception type, transaction boundaries, proxying, propagation and actual database behavior.
I would first identify where the latency is coming from instead of immediately adding more CPU or caching.
I would inspect:
Distributed tracing is particularly useful when the API calls multiple dependencies.
A typical investigation might reveal:
API Latency: 4.8 seconds
Database: 3.5 sec
Payment API: 0.9 sec
Serialization: 0.2 sec
Application: 0.2 sec
In this case, increasing the application's CPU would not solve the primary bottleneck.
I would inspect active, idle and pending connections along with connection acquisition time.
Possible causes include:
Increasing the pool size blindly can simply move the bottleneck to the database.
I would first identify why connections are being held for too long.
A useful investigation is:
Application
|
v
HikariCP
|
+---- Active Connections
|
+---- Pending Requests
|
v
Database
N+1 occurs when an initial query retrieves N entities and additional queries are executed while accessing related data.
For example:
SELECT * FROM orders;
SELECT * FROM customer WHERE id = 1;
SELECT * FROM customer WHERE id = 2;
SELECT * FROM customer WHERE id = 3;
...
SELECT * FROM customer WHERE id = N;
Depending on the use case, solutions include:
Simply changing everything to eager loading can create a different performance problem. The solution should match the API's actual data requirements.
A typical design uses an authorization server or identity provider to issue JWT access tokens. The Spring Boot API acts as a resource server and validates the token.
Validation should cover relevant properties such as:
Authorization rules should be enforced at the API boundary and, where appropriate, at the service or method level.
Client
|
| JWT Access Token
v
API Gateway
|
v
Spring Boot Resource Server
|
+---- Validate Token
|
+---- Check Authorities
|
v
Business Logic
First identify the OAuth2 role of the application.
A browser application performing user login may act as an OAuth2 client, while a backend API commonly acts as a resource server.
The authorization server or identity provider handles authentication and token issuance. The resource server validates access tokens and enforces authorization.
OAuth2 provides authorization and delegated access mechanisms, while OpenID Connect adds an identity layer for user authentication.
I would use synchronous communication when an immediate response is required and asynchronous messaging when work can be decoupled.
Client
|
v
Order Service
|
+---- REST / gRPC ----> Product Service
|
+---- Kafka Event ----> Notification Service
Every synchronous dependency should have a timeout, and critical calls may require circuit breakers, retries and bulkheads.
The choice should be driven by business requirements and failure characteristics.
| REST | Kafka |
|---|---|
| Immediate request-response | Asynchronous processing |
| Simple queries and commands | Event-driven workflows |
| Caller needs an immediate result | Multiple consumers can react independently |
| Simple interaction model | Useful for decoupling and buffering workloads |
For example, retrieving product information may naturally be synchronous.
Sending an OrderCreated event to Notification and Analytics services may be better handled asynchronously.
I would combine:
For example, if a Recommendation Service is unavailable, the Product Service should ideally still return product information rather than failing the entire request.
Product Request
|
+---- Product Service → Success
|
+---- Recommendation → Failure
|
v
Degraded Response
Each mechanism solves a different problem.
Retries should never be unlimited.
Retrying non-idempotent operations without protection can create duplicate business effects.
For example:
Request
|
v
Payment Service
|
X Timeout
|
Retry?
|
+---- Must be idempotent
|
v
Payment Provider
I would use an idempotency key for operations where duplicate execution has business consequences.
POST /orders
Idempotency-Key: order-request-123
The service stores the key and the outcome.
If the same request arrives again, the service returns the existing result instead of creating another order.
This is particularly important for:
A Saga breaks a distributed business transaction into local transactions.
Order Created
|
v
Payment Completed
|
v
Inventory Reserved
|
v
Order Confirmed
If inventory reservation fails after payment succeeds, a compensating action can refund or cancel the payment.
Sagas can be implemented using:
For complex workflows, orchestration can make the overall state transitions easier to understand and monitor.
Each service should own its data and expose business operations through APIs or events rather than allowing other services to directly modify its tables.
For database changes that must reliably produce an event, the Transactional Outbox Pattern can be used.
Local Transaction
|
+---- Update Business Data
|
+---- Write Outbox Event
|
v
Event Publisher
|
v
Kafka
Eventual consistency is often acceptable for derived data, notifications and search indexes, while critical business invariants should be explicitly protected.
I would assume that consumers can see duplicate events and make the business operation idempotent.
Event ID: EVT-1001
First delivery
|
v
Process Event
|
v
Save EVT-1001
Second delivery
|
v
Detect EVT-1001
|
v
Skip Duplicate Effect
A consumer can persist the event ID or business idempotency key.
Database uniqueness constraints can provide another layer of protection.
The key principle is that duplicate delivery should not result in duplicate business effects.
Consumer lag means consumers are not keeping up with the messages being produced.
I would check:
If processing is CPU or I/O bound, scaling consumers may help, provided the topic has enough partitions.
If the bottleneck is a database, simply adding consumers can increase database pressure instead of solving the root problem.
A typical investigation might look like:
Kafka
|
+---- Partition 0 → Consumer 1
|
+---- Partition 1 → Consumer 2
|
+---- Partition 2 → Consumer 3
Lag Increasing
|
v
Check Consumer Throughput
|
v
Check Processing Dependency
|
v
Scale or Fix Bottleneck
In Kubernetes, Services and DNS can provide service discovery.
order-service
payment-service
inventory-service
Applications communicate with logical service names rather than individual pod IP addresses.
In other environments, a dedicated service registry can be used.
The architecture should also account for instance health, load balancing and service lifecycle.
The API Gateway provides a controlled entry point into the backend.
Common responsibilities include:
API Gateway
|
+--------------+--------------+
| | |
v v v
Product Order Customer
Service Service Service
Business logic should generally remain in the services rather than accumulating inside the gateway.
I would choose the AWS compute platform based on operational requirements.
Users
|
Route 53
|
Load Balancer
|
ECS / EKS
|
Spring Boot Microservices
|
+-----------+-------------+
| | |
RDS ElastiCache Kafka
Container images can be stored in Amazon ECR.
Application configuration and secrets should be managed using appropriate AWS services rather than hard-coded into container images.
CloudWatch, application metrics and distributed tracing can provide operational visibility.
| ECS | EKS |
|---|---|
| AWS-managed container orchestration | Managed Kubernetes control plane |
| Typically simpler operational model | Kubernetes ecosystem and APIs |
| Good when Kubernetes-specific capabilities are not required | Useful when the organization standardizes on Kubernetes |
The decision should consider:
I would avoid placing critical application capacity in a single Availability Zone.
Load Balancer
/ \
/ \
AZ-A AZ-B
| |
App Instances App Instances
\ /
\ /
Shared Data
Application instances should be distributed across multiple Availability Zones behind a load balancer.
Stateful dependencies such as databases also need an appropriate high-availability strategy.
High availability is not just about running two application instances. The database, cache, messaging and network layers also need to be considered.
I would use an appropriate RDS high-availability configuration and ensure that the application can handle database failover without relying on a fixed database instance address.
The application should also use sensible connection-pool settings, timeouts and controlled retry behavior.
Database HA does not eliminate application-level failures.
Connection pools, transactions, slow queries, locks and database resource utilization still need to be monitored.
ElastiCache can reduce repeated reads against the primary database for suitable workloads.
A common cache-aside flow is:
Request
|
v
Check Cache
/ \
Hit Miss
| |
Return Database
|
v
Cache
|
v
Return
The design should define:
A cache should improve a known workload problem rather than hide inefficient database queries.
I would monitor both infrastructure and application-level signals.
Important metrics include:
Logs provide detailed events, metrics provide trends and alerts, and distributed tracing shows how a request travels across services.
The goal is to correlate these signals during an incident rather than investigating each system in isolation.
Request
|
v
API Gateway
|
v
Order Service
|
+---- Trace
|
+---- Logs
|
+---- Metrics
|
v
Payment Service
|
v
Database
I would follow a structured investigation rather than immediately changing configuration.
Check request rate, p50, p95 and p99 latency and error rate.
Inspect CPU, memory, container restarts, network metrics and scaling events.
Look for garbage-collection pressure, thread contention, blocked threads and unusual CPU usage.
Inspect thread pools, request processing, connection pools and downstream client metrics.
Look for slow queries, lock contention, connection-pool exhaustion, increased query volume and changes in execution plans.
Use distributed traces to identify whether another microservice or external API is responsible for the latency.
Review deployment events, load balancer behavior, scaling events, security and network changes and infrastructure alarms.
The key is to correlate the timeline and verify hypotheses using metrics, logs and traces.
For example, if latency increased immediately after a deployment, I would compare the new deployment against the previous version rather than assuming that the database is responsible.
A production-oriented architecture could look like this:
Users
|
v
Route 53 / CDN
|
v
Load Balancer
|
v
API Gateway
|
+-------------------+-------------------+
| | | | |
v v v v v
Product Customer Cart Order Search
| | |
| | +------+
| | |
v v v
Redis Redis Kafka
/ | \
/ | \
v v v
Payment Inventory
| |
+---+---+
|
v
RDS
Kafka → Notification / Analytics / Other Consumers
Use timeouts, circuit breakers, bounded retries and bulkheads for synchronous dependencies.
Non-critical functionality should degrade gracefully.
For example, a recommendation failure should not necessarily prevent checkout.
Payment operations should be idempotent.
The system should maintain explicit payment states and reconcile uncertain outcomes with the payment provider rather than blindly retrying a potentially completed payment.
Each service owns its data.
Cross-service workflows use events and Saga-style compensation where required.
Consumers should be idempotent using event IDs, business keys and database constraints where appropriate.
The relational database should have an appropriate high-availability configuration.
Application services should use connection timeouts, pool limits and controlled retry behavior.
Metrics, structured logs and distributed traces should be correlated using request and trace identifiers.
Use rolling or similarly controlled deployments, readiness checks, graceful shutdown and backward-compatible database migrations.
Senior-level answer: The architecture is not complete until failure scenarios are designed. Explain not only how a successful order works, but also what happens when payment fails, inventory is unavailable, Kafka delivers a duplicate event, Redis is unavailable, traffic suddenly increases, or the database becomes slow.
At senior level, naming a technology is not enough.
A strong answer explains why the technology is needed, what trade-offs it introduces, and how the system behaves when something fails.
For example, saying "I will use Kafka" is only the beginning.
A strong candidate should explain:
Yes. They cover Java internals, Spring Boot, databases, microservices, Kafka, AWS, observability and system design.
No. Use them as a framework. In a real interview, explain the reasoning behind your design and adapt the answer to the constraints given by the interviewer.
No. Kafka is valuable for asynchronous and event-driven workflows, but synchronous REST or gRPC can be simpler when an immediate response is required.
Failure handling. A strong design explains what happens when dependencies become slow, messages are duplicated, databases fail, traffic spikes, or services restart.
Discuss compute, networking, load balancing, Availability Zones, database availability, caching, messaging, observability, security, scaling and deployment strategy.
Senior Java backend interviews increasingly test the complete engineering lifecycle rather than isolated framework knowledge.
You should be able to move from Java internals to Spring Boot behavior, from database performance to microservices resilience, and from application code to AWS infrastructure and production troubleshooting.
The strongest answers are not the ones that mention the most technologies. They are the ones that explain the trade-offs, failure modes, scalability limits and recovery strategies behind each decision.
0 Comments