REST API Methods - Basic Fundamental
REST API Methods - Basic Fundamental
POST — Create a new resource, or trigger an action. You're sending data to the server and
asking it to process it. Every call can produce a different result — call POST /orders twice and
you'll get two separate orders. Not safe, not idempotent. The server decides what to do with
the payload.
PUT — Replace a resource completely. You send the entire object, and the server replaces
whatever was there. Call PUT /users/5 with the same payload twice — the result is the same.
Idempotent. If the resource doesn't exist, many implementations create it.
PATCH — Update part of a resource. You send only the fields that changed. Unlike PUT, this is
a partial update. Whether it's idempotent depends on what the patch does — more on this
below.
DELETE — Remove a resource. Call DELETE /users/5 and the user is gone. Call it again — still
gone. The state of the system after the first call and the tenth call is identical. Idempotent.
OPTIONS — Ask the server what it supports. Browsers send this automatically before cross-
origin requests (the CORS preflight). It asks: "What methods do you allow on this URL?" The
server responds with Allow: GET, POST, PUT, DELETE in the headers. Your frontend code
rarely calls this manually — the browser does it behind the scenes.
HEAD — Same as GET, but returns only headers, no body. Used to check if a resource exists,
or to get its Content-Length before deciding whether to download it. Never has a response
body.
What is Idempotency?
Idempotency means: calling the same operation multiple times produces the same result as
calling it once.
A light switch with labeled ON and OFF positions is idempotent — press ON five times, the
light is still just ON. A toggle button is not idempotent — press it five times and you don't
know if the light is on or off.
In REST terms: if retrying a request due to a network timeout produces the same server state
as sending it once — that's idempotent. If retrying creates duplicates or causes extra side
effects — that's not idempotent.
Safe is a related concept. A method is safe if it causes no side effects — it only reads, never
writes. Safe methods are always idempotent. Idempotent methods are not always safe
(DELETE changes state but calling it repeatedly is stable).
Note that PATCH is intentionally excluded from the table above — it deserves its own
explanation.
It depends on what the patch instruction says. Consider two types of PATCH requests:
PATCH /users/5 with body { "name": "Shakib" } — this is effectively idempotent. Run it
ten times, the name is "Shakib" every time.
PATCH /posts/3 with body { "views": "+1" } (increment views) — this is not idempotent.
Every call increments the counter. 10 calls = 10 extra views.
The HTTP spec does not guarantee PATCH is idempotent — it explicitly leaves it up to the
operation. That's the critical distinction. PUT sends the full replacement object, so
repeating it always produces the same state. PATCH sends a diff, and diffs can be absolute or
relative. Relative diffs are not idempotent.
GET /users/5 — user exists → 200 OK. User doesn't exist → 404 Not Found.
POST /users — resource created → 201 Created with the new resource in body and a Location
header pointing to /users/5. Request body is malformed JSON → 400 Bad Request. JSON is
valid but a required field is missing → 422 Unprocessable Entity. A user with that email
already exists → 409 Conflict.
PUT /users/5 — full replace succeeded → 200 OK (if body returned) or 204 No Content (if no
body). Resource didn't exist but was created → 201 Created. Sent the wrong Content-Type →
415 Unsupported Media Type.
PATCH /users/5 — partial update succeeded → 200 OK with updated resource, or 204 No
Content. Resource not found → 404 Not Found. Invalid field values → 422 Unprocessable
Entity.
DELETE /users/5 — deleted successfully → 204 No Content (no body needed). Resource not
found → 404 Not Found. Some APIs return 200 OK with a message body on DELETE — both
are acceptable conventions, but 204 is more common.
No token sent at all → 401 Unauthorized ("you haven't told me who you are")
Token sent but the user doesn't have permission → 403 Forbidden ("I know who you are,
but you can't do this")
This distinction is the most commonly confused in interviews. 401 = identity unknown. 403 =
identity known, access denied.
2xx — Success
200 -OK-GET request returned data. PUT/PATCH succeeded and body is returned. DELETE with a message body.
201-Created-POST created a new resource. Include Location header pointing to the new resource. Body should contain the
created object.
204-No Content-DELETE succeeded. PUT/PATCH succeeded with no body to return. Success but nothing to show. Never
has a response body.
202-Accepted-Request received and queued for async processing. Not done yet — job is in progress. Use for long-running
operations like report generation, email sending.
3xx — Redirection
301-Moved Permanently-Resource has permanently moved to a new URL. Browsers and clients should update bookmarks.
Used in domain migrations.
304-Not Modified-Client's cached version is still fresh. Sent in response to conditional GET (If-None-Match / If-Modified-
Since). No body returned.
400-Bad Request-Malformed JSON, invalid syntax, missing required query params. The request itself is broken — server
can't parse it. General catch-all for bad input.
401-Unauthorized-No authentication provided, or token is expired/invalid. "I don't know who you are." Client must
authenticate first. Often returned with WWW-Authenticate header.
403-Forbidden-Authenticated but not authorized. "I know who you are, but you can't access this." Role/permission denied.
ADMIN endpoint called by USER role.
404-Not Found-Resource doesn't exist at this URL. GET/PUT/DELETE on an ID that doesn't exist in the database. Also used
to hide existence of protected resources.
405-Method Not Allowed-HTTP method not supported on this endpoint. Calling DELETE on /users when DELETE isn't
implemented. Server must return Allow header listing valid methods.
409-Conflict-Resource already exists. POST to create a user with an email that's already registered. Concurrent edit conflict
(optimistic locking). Duplicate unique constraint violation.
415-Unsupported Media Type-Sent wrong Content-Type header. Server expects application/json but received text/plain or
multipart/form-data without support.
422-Unprocessable Entity-Request is syntactically valid JSON but semantically invalid. Age field has value -5. Email field
passes as a string but isn't an email format. Business rule violations. Use this over 400 when you understood the payload but
rejected it for logical reasons.
429-Too Many Requests-Client has exceeded the rate limit. Usually returned with Retry-After header. Common in public
APIs, login endpoints, OTP services.
500-Internal Server Error-Unhandled exception on the server. NullPointerException, database query failure, unexpected
state. Client did nothing wrong. Generic server crash.
502-Bad Gateway-Your API gateway or reverse proxy (Nginx, AWS ALB) received an invalid response from the upstream
service. The gateway is alive; the backend behind it is not responding properly.
503-Service Unavailable-Server is temporarily down — maintenance window, overloaded, or a circuit breaker tripped.
Expected to be temporary. Clients should retry after Retry-After header value.
504-Gateway Timeout-Proxy/gateway waited too long for the upstream service to respond and gave up. Common in
microservices when one service calls another and it hangs. Feign Client timeout scenario.
The Three Pairs That Get Confused Most in Interviews
400 vs 422 — Both are client errors with a request body. The difference is where the problem
is. 400 means the server couldn't even parse the request — broken JSON, missing Content-
Type, invalid URL structure. 422 means the server parsed it fine, understood it, but the data
inside fails validation — an age of -5, a phone number with letters, a date range where end is
before start. In Spring Boot with @Valid, MethodArgumentNotValidException maps to 400
by default, but many teams remap it to 422 because the semantics fit better.
401 vs 403 — Both block access, but for different reasons. 401 means the server has no
identity for the caller — no token, expired token, invalid token. The fix is to log in. 403 means
the server knows exactly who you are but your role doesn't allow this action. A USER hitting
an ADMIN endpoint gets 403. The fix is not to log in again — the fix is to get a different role or
not make that call.
204 vs 200 on DELETE — Convention, not a hard rule. 204 is more semantically correct
because there's nothing to return after deletion. 200 is acceptable if you want to send a
confirmation message body. Most production APIs use 204 for DELETE. Both are right — but
know that 204 never has a body. If you send a 204 with a body, clients are supposed to
ignore the body.
Every operation in my HMS project maps directly to these rules. When a doctor is not found
— 404. When a user tries to access an admin endpoint — 403. When JWT is expired — 401.
When creating a patient with a duplicate record — 409. When the appointment date field has
an invalid format — 422. When the hospital service is down and the gateway waits — 504.
That is the full picture. No memorization list — just understanding the reason behind each
code.
Your entire API is one single URL. Everything goes to that one endpoint. The HTTP method
doesn't matter — everything is POST.
POST /api
POST /api
POST /api
All three calls go to the same URL. The server figures out what to do by reading the body —
<action>getUser</action>, <action>createOrder</action>. HTTP is just a tunnel here, nothing
more.
Level 1 — Resources
Now you have separate URLs for separate things. No more single endpoint. But you still use
POST for everything — GET, delete, update — all POST.
Right direction — at least resources are separated. But HTTP methods are still ignored.
Now you use the correct HTTP method for the correct action. GET to read, POST to create,
PUT to update, DELETE to delete. Proper status codes — 201, 404, 204. This is what you build
in Spring Boot every day.
Most production APIs, including my HMS project, are at Level 2. This is considered good
REST by most teams.
This is where the API tells the client what it can do next. Every response includes links — not
just data.
Real example — you call GET /users/5 and the response is:
json
"id": 5,
"name": "Shakib",
"email": "shakib@[Link]",
"_links": {
The client does not need to know any URLs in advance. The API guides it. Like a website —
you land on a homepage and follow links. You don't type URLs manually.
Why interviewers ask this — they want to know if you understand that most real-world APIs
are Level 2, not Level 3. HATEOAS is rarely implemented fully in practice because it adds
complexity. But knowing the concept and being able to explain the _links block is enough to
impress.
One line answer for interview — Richardson Maturity Model has 4 levels. Level 0 is single
endpoint, Level 1 adds resources, Level 2 adds proper HTTP verbs and status codes — which
is what most production APIs are. Level 3 adds HATEOAS where responses include links to
guide the client's next actions.
You go to a restaurant and say "can I get this dish without onions?" The waiter either says
yes and brings it without onions, or says "sorry, it comes pre-made, can't change it." That
negotiation between you and the kitchen — that is content negotiation.
The client sends an Accept header in the request telling the server what format it can
handle.
GET /users/5
Accept: application/json
Server reads that header and sends back JSON with Content-Type: application/json.
GET /users/5
Accept: application/xml
The same endpoint, same URL — but now the server sends XML back if it supports it.
If the server cannot produce what the client asked for — it returns 406 Not Acceptable. This
is the status code most freshers don't know exists.
Accept — sent by the client in the request. This is what I can receive.
Content-Type — sent by both sides. In a request body it means this is what I am sending you.
In a response it means this is what I am sending back.
These two are always confused in interviews. Remember — Accept is about what you want to
receive. Content-Type is about what you are currently sending.
In Spring Boot
java
@GetMapping(
value = "/users/{id}",
Same URL, same method — different output based on what the client asked for.
Because most freshers think one endpoint always returns one format. Content negotiation
shows you understand that HTTP is designed to be flexible — the same resource can be
represented in multiple formats.
One line answer for interview — Content negotiation is the process where the client tells the
server what format it wants using the Accept header, and the server responds in that format
using Content-Type. If the server cannot produce the requested format it returns 406.
Your API is live. Clients — mobile apps, frontend, third party integrations — are consuming it.
Now you need to change a response structure, rename a field, or remove something. If you
change it directly, every existing client breaks. Versioning solves this — old clients keep
using v1, new clients use v2.
GET /api/v1/users
GET /api/v2/users
Most commonly used in real projects. Easy to see, easy to test in browser, easy to document.
Your HMS project should use this.
Downside — URL should ideally represent a resource, not a version. Purists argue this is not
clean REST. But practically, everyone uses it.
GET /api/users?version=1
GET /api/users?version=2
Easy to implement. But messy in large APIs — every URL needs the param. Not commonly
used in production.
GET /api/users
X-API-Version: 1
GET /api/users
X-API-Version: 2
Clean URL — the resource path stays the same. Used by companies like Microsoft. Downside
— not visible in the browser URL, harder to test quickly, harder to document.
Version is embedded inside the Accept header — also called content negotiation versioning.
GET /api/users
Accept: application/[Link].v1+json
GET /api/users
Accept: application/[Link].v2+json
Most RESTfully correct approach — because the URL represents the resource and the
version is part of the representation format. Used by GitHub's API.
Most teams use URI versioning because it is simple, visible, and easy to maintain. Header
versioning is cleaner architecturally. The right answer in an interview is — "it depends on the
team's preference, but URI versioning is the most practical and widely adopted."
@GetMapping("/api/v1/users")
@GetMapping("/api/v2/users")
One line answer for interview — API versioning ensures existing clients don't break when the
API changes. The four strategies are URI versioning, request parameter versioning, header
versioning, and media type versioning. URI versioning is most commonly used in practice.
You already know POST is not idempotent. Now think about this real scenario —
A user clicks Place Order on a payment page. The request goes to the server. The server processes the
payment, charges the card, creates the order — but the response never reaches the client due to a network
timeout. The user sees an error screen. They click Place Order again.
Now the server processes it a second time. The card gets charged twice. Two orders created. The user is
furious.
It is a unique ID generated by the client and sent with the request. The server uses this key to recognize — "I
have already processed this exact request before. I will not process it again. I will just return the previous
result."
Step 1 — Client generates a unique key before sending the request. Usually a UUID.
POST /payments
Idempotency-Key: 7f3b2a1c-9d4e-4f8a-b6c2-1a2b3c4d5e6f
Content-Type: application/json
Step 3 — Server receives the request. It checks its storage — "have I seen this key before?"
If no — process the payment, store the key with the result, return the response.
If yes — do not process again. Just return the previously stored response directly.
Step 4 — Client retries due to timeout. Same key is sent again. Server recognizes it, returns the same result.
No duplicate charge.
Usually in Redis or a database table. The key is stored with a TTL — time to live. After 24 hours the key
expires. This prevents the storage from growing forever.
key: 7f3b2a1c-9d4e-4f8a-b6c2-1a2b3c4d5e6f
expires: 24 hours
Stripe — their payment API requires an Idempotency-Key header on every POST request. If you retry with
the same key within 24 hours, Stripe returns the original response without charging again.
If the client sends the same idempotency key but with a different request body — that is a conflict. Server
returns 422 Unprocessable Entity or 409 Conflict depending on implementation. Same key must always
mean same request.
Because payment systems, order systems, booking systems — all critical flows — need this. A fresher who
knows idempotency keys signals they think about real production problems, not just CRUD.
One line answer for interview — An idempotency key is a unique ID sent by the client in the request header.
The server uses it to detect duplicate requests and return the original result without processing again. This
prevents double charges or duplicate records in non-idempotent operations like POST.
An ATM allows you to withdraw a maximum of 3 times per day. On the 4th attempt it says — "daily limit
reached, try tomorrow." The ATM is not broken. Your account is fine. You simply crossed the allowed limit.
That is rate limiting.
Why it exists
One — a single client can flood your server with thousands of requests per second and crash it. This is a
DDoS attack scenario.
Two — one heavy user consumes all server resources, slowing down every other user.
Three — your paid third party services — SMS, email, payment gateway — get called excessively and your
bill explodes.
Rate Limiting — hard stop. You crossed the limit, request is rejected. No processing happens. Returns 429
Too Many Requests.
Throttling — soft slow down. You are making too many requests so the server starts processing them
slower — queuing them, adding delays. It does not reject — it slows down. Think of it like a highway with a
speed limit versus a highway that gets congested and everyone slows down naturally.
In practice — rate limiting is what most REST APIs implement. Throttling is more common inside internal
systems and message queues.
Fixed Window — count requests in a fixed time window. Allow 100 requests per minute. Counter resets
every minute. Simple but has a burst problem — 100 requests in the last second of minute 1 and 100 in the
first second of minute 2 means 200 requests in 2 seconds.
Sliding Window — smoother version. Instead of a fixed reset, the window slides continuously. More
accurate, no burst problem.
Token Bucket — the most commonly used algorithm. Imagine a bucket that holds tokens. Every request
consumes one token. Tokens refill at a fixed rate. If the bucket is empty, request is rejected. Allows short
bursts but controls the average rate. Stripe and AWS use this.
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000060
Retry-After — wait this many seconds before retrying. X-RateLimit-Limit — total allowed requests in the
window. X-RateLimit-Remaining — how many you have left. X-RateLimit-Reset — Unix timestamp when the
counter resets.
In Spring Boot
Spring Boot does not have built-in rate limiting. You implement it using —
Bucket4j — most popular Java library. Implements token bucket algorithm. Can store state in Redis for
distributed systems.
API Gateway level — in microservices, rate limiting is usually handled at the API Gateway — Spring Cloud
Gateway, AWS API Gateway, Kong. The individual services don't worry about it.
For your interview — knowing that rate limiting is best handled at the gateway level in microservices is a
strong answer.
Answer — rate limit by IP. Allow maximum 5 requests per hour per IP. After that return 429 with Retry-After:
3600. This prevents brute force attacks on the OTP system.
One line answer for interview — Rate limiting restricts the number of requests a client can make in a time
window and returns 429 when exceeded. Throttling slows down processing instead of rejecting. In
microservices, rate limiting is handled at the API Gateway level using algorithms like token bucket.
In microservices you have multiple services calling each other. Your Order Service calls
Payment Service. Payment Service calls Bank API.
Now imagine the Bank API goes down or becomes very slow — taking 30 seconds to respond.
Every request to Payment Service now hangs for 30 seconds waiting. Order Service is waiting.
Users are waiting. Threads are piling up. Eventually your entire application crashes — not
because of your code, but because one external service is slow.
This is called a cascading failure. One service failing brings down everything connected to it.
Your house has a circuit breaker in the electrical panel. When there is a power surge or short
circuit — the breaker trips and cuts the electricity. It does not let the surge travel through the
wires and burn your appliances. Once the problem is fixed you reset the breaker and
electricity flows again.
Same concept in software — when a downstream service is failing, the circuit breaker trips
and stops sending requests to it. Instead of waiting and crashing, it fails fast and returns a
fallback response immediately.
Requests flow through normally. The circuit breaker is silently counting failures in the
background. If failures stay below the threshold — everything stays closed. This is the default
state.
Now — every incoming request is immediately rejected without even trying to call the
downstream service. It returns a fallback response instantly. No waiting. No hanging threads.
Fast failure.
The fallback could be — a cached response, a default message, an empty list, or a proper error
message like "payment service temporarily unavailable."
After a configured timeout — say 30 seconds — the circuit breaker moves to HALF-OPEN
automatically. It allows one or a few test requests through to check if the downstream service
has recovered.
If the test request succeeds — circuit moves back to CLOSED. Normal flow resumes.
If the test request fails — circuit goes back to OPEN. Waits another 30 seconds. Tries again.
The library used is Resilience4j. Previously it was Netflix Hystrix but that is now deprecated.
Resilience4j is the current standard.
Configuration in [Link]:
yaml
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
java
return [Link](request);
Because in microservices, inter-service calls are everywhere. Not knowing circuit breaker
means you don't understand how to build resilient systems. In your E-Commerce project —
Order Service calling Payment Service is the perfect real example to give.
One line answer for interview — "Circuit Breaker prevents cascading failures in microservices.
It has three states — Closed where requests flow normally, Open where requests are rejected
fast with a fallback when failure threshold is crossed, and Half-Open where it tests if the
service recovered. In Spring Boot we use Resilience4j to implement this."
Your API returns a list of 1000 products. Each product has name, description, price, category, images — the
response JSON is 2MB in size. Every user who loads that page downloads 2MB. On a slow mobile network
that takes 10 seconds. Multiply that by 10,000 users — your bandwidth bill is huge and your app feels slow.
Compression solves this — the server compresses the response before sending it. The same 2MB response
becomes 200KB. 10x smaller. Faster transfer. Less bandwidth cost.
The real world analogy
You need to send 10 books to a friend by courier. Shipping 10 separate books costs a lot. Instead you
vacuum-pack them — same content, much smaller package, cheaper shipping. Your friend opens the
package and gets all 10 books. That is compression.
The client sends an Accept-Encoding header saying — I can handle compressed responses.
GET /products
The server compresses the response body using gzip, sends it back with a Content-Encoding header saying
— "I compressed this with gzip."
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Type: application/json
The client receives the compressed bytes and automatically decompresses them. This all happens
transparently — your frontend code and Postman handle it automatically. You never see the compressed
data manually.
gzip — most widely used. Good compression ratio. Supported everywhere. This is what you will use 99% of
the time.
br — Brotli. Newer, better compression than gzip. Used by modern browsers. Not all HTTP clients support it
yet.
Response compression — server compresses what it sends back. This is the common one. Reduces
response size.
Request compression — client compresses what it sends to the server. Used when uploading large
payloads — bulk data imports, file uploads. Less common. Client sends Content-Encoding: gzip header with
compressed body.
In Spring Boot
Spring Boot makes this extremely simple. One property in [Link] enables it:
properties
[Link]=true
[Link]-types=application/json,application/xml,text/plain
[Link]-response-size=1024
enabled=true — turn on compression. mime-types — only compress these content types. No point
compressing images — they are already compressed. min-response-size=1024 — only compress responses
larger than 1KB. Small responses don't benefit from compression — the overhead of compressing is more
than the saving.
That is all. Spring Boot handles everything else automatically using the embedded Tomcat server.
Images — JPEG, PNG, WebP are already compressed formats. Compressing them again gives almost no
benefit and wastes CPU.
Already encrypted data — compressed encrypted data does not compress further.
Very small responses — responses under 1KB are not worth compressing. The compression overhead costs
more than it saves.
Plain text JSON — compresses to about 10-20% of original size. A 1MB JSON response becomes roughly
100-200KB. This is because JSON has a lot of repetition — field names repeat on every object in an array.
gzip is extremely good at compressing repetitive text.
A complete answer covers three things — enable gzip compression, add pagination so you don't return
1000 records at once, and add caching. Knowing all three together makes a strong answer.
One line answer for interview — gzip compression reduces response size by up to 90% for JSON data. The
client sends Accept-Encoding: gzip, the server compresses the body and responds with Content-Encoding:
gzip. In Spring Boot it is enabled with a single property — [Link]=true.
Normal REST is request-response. Client asks, server answers, connection closes. Done.
But what if the client needs to know when something happens on the server — without asking every time?
Examples —
A food delivery app showing live order status. A notification bell showing new alerts. A stock price updating
in real time. A chat message arriving instantly.
These are server to client push scenarios. Normal REST cannot do this efficiently. These three techniques
solve it in different ways.
Technique 1 — Short Polling (baseline — not in your list but needed for comparison)
Simple but wasteful. 99% of requests return nothing. Wastes bandwidth, wastes server resources. Like
texting someone "are you there?" every 5 seconds.
Client sends a request. Server does NOT respond immediately. It holds the connection open and waits until
something actually happens — then responds.
Connection closes.
Client immediately sends another long poll request.
Better than short polling — no empty responses. But still request-response underneath. Every response
closes the connection and client must reconnect immediately.
Real analogy — you call a restaurant to ask if your table is ready. Instead of saying "call back in 5 minutes"
they say "stay on hold, we'll tell you the moment it's ready." One call, one answer, then you hang up and
call again for the next wait.
When used — when WebSockets are not available or too complex. Simple notification systems. Chat
applications before WebSockets became standard.
Downside — server holds thousands of open connections waiting. High memory usage. Not scalable for
very high traffic.
Server holds the connection open and keeps streaming events to the client as they happen. Connection
stays open permanently. Server pushes whenever it wants. Client just listens.
One connection, multiple events. Unlike long polling — connection does not close after each event.
Key characteristics —
One direction only — server to client. Client cannot send data back on the same connection.
Real analogy — subscribing to a YouTube channel's live stream. You connect once and the stream keeps
coming to you. You don't reconnect for each video frame.
When used — live notifications, real-time dashboards, live score updates, order tracking, stock price feeds.
In Spring Boot —
@GetMapping(value = "/notifications/stream”,
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
return emitter;
TEXT_EVENT_STREAM_VALUE is the content type for SSE. Spring Boot has built-in SseEmitter support.
Technique 4 — Webhooks
Completely different direction. Instead of the client asking the server — the server calls the client when
something happens.
You register a URL with the server — "when something happens, POST to this URL." The server stores your
URL. When the event occurs, the server makes an HTTP POST request to your URL with the event data.
Real analogy — instead of you calling the bank every day to check if your salary arrived — you give the bank
your phone number and they call you the moment it arrives.
When used — payment notifications (Stripe, Razorpay), GitHub events (push, PR, merge), SMS delivery
receipts, email open tracking. Any scenario where two separate servers need to communicate on events.
Downside — your server must be publicly accessible. Your URL must be live and reachable. If your server is
down when the webhook fires, you miss the event — so you need retry logic on the sender side.
Technique Direction Connection Best for Scalability
closes frequency
repeatedly checks
Long Polling Client → Server Held open until Simple real-time, Medium
duplex collaborative
tools
Note WebSockets is added in the table for completeness — it is the full-duplex upgrade beyond SSE, used
for chat and gaming where both sides send data simultaneously.
How would you implement real-time order tracking in your E-Commerce project?
Strong answer — "When an order status changes, the backend pushes an update to the frontend using SSE.
The frontend connects once to GET /orders/{id}/stream and receives status updates as they happen —
confirmed, packed, shipped, delivered — without polling repeatedly."
One line answer for interview — Long polling holds the connection until an event occurs then closes. SSE
keeps the connection open and streams multiple events from server to client over HTTP. Webhooks are
server-to-server — one server registers a URL and the other POSTs to it when events happen. SSE is best
for live UI updates, Webhooks for backend-to-backend event notification.
Soft delete is moving a document to the recycle bin. It looks deleted to the user. But it is still there. You can
restore it if needed.
In banking, healthcare, legal systems — you legally cannot delete records. If a patient record is deleted and
there is a court case, you are in trouble. Soft delete keeps the record while hiding it from normal usage.
A user deletes their account by mistake and contacts support. With hard delete — impossible to recover.
With soft delete — support sets is_deleted = false and the account is back.
Your orders table has a user_id foreign key. If you hard delete the user, all their orders lose their reference
— your database throws a foreign key constraint violation or you end up with orphaned records. Soft
delete avoids this entirely.
Business teams need historical data. "How many users registered last year including those who later
deleted their accounts?" Hard delete makes this impossible. Soft delete keeps the data available for
reports.
When deleted — set is_deleted = true. Every query adds WHERE is_deleted = false.
When deleted — set deleted_at = current timestamp. NULL means active. Non-null means deleted. This
approach also tells you when it was deleted — more useful for auditing.
The REST API implications — what interviewers actually want to know
This is where most freshers miss the point. The interviewer is not asking about database design. They want
to know how soft delete affects your REST API behavior.
With soft delete your DELETE /users/5 endpoint does not actually delete from the database. It sets
deleted_at = now(). Returns 204 No Content — same as hard delete from the client's perspective. The client
does not know or care whether it was hard or soft deleted.
GET /users/5 on a soft deleted user — should return 404 Not Found. Even though the record exists in the
database, it is logically deleted. The API must behave as if it does not exist.
GET /users — must filter out soft deleted records. WHERE deleted_at IS NULL.
User is soft deleted. Someone tries to register with the same email. Your unique constraint on email will
pass — the old record exists with that email. You need to handle this in your service layer — check if a soft
deleted record exists with that email and decide whether to reactivate it or reject the new registration.
Manual approach — add deleted_at field to entity, filter in every query manually.
@Entity
@SQLRestriction("deleted_at IS NULL")
@Column(name = "deleted_at")
Now every JPA query on User automatically adds deleted_at IS NULL condition. You never accidentally
fetch deleted records. Clean and safe.
[Link]([Link]());
[Link](user);
Temporary data — OTP records, session tokens, password reset tokens. These should be hard deleted after
use.
GDPR compliance — when a user formally requests data erasure under GDPR, you must hard delete their
personal data. Soft delete does not satisfy GDPR right to erasure.
Log and audit entries — these should never be deleted at all, soft or hard.
"A user deletes their account. Six months later they want to come back and register with the same email.
How do you handle this?"
Strong answer — With soft delete the record exists with deleted_at set. When the same email tries to
register, the service checks for a soft deleted record with that email. If found within a grace period — say
30 days — we reactivate the account. If it has been longer we can either reactivate or create fresh based on
business rules. This is much cleaner than hard delete where the email is simply free to reuse but all history
is lost.
One line answer for interview — Soft delete marks a record as deleted using a flag or timestamp without
removing it from the database, preserving audit trail and referential integrity. Hard delete permanently
removes the record. From the REST API perspective the behavior is identical to the client — DELETE
returns 204 and GET returns 404 — but internally the data is retained for auditing and recovery.
"id": 5,
"name": "Shakib",
"email": "shakib@[Link]",
"phone": "9876543210",
"address": "Hyderabad",
"profilePicture": "[Link]
"dateOfBirth": "1998-01-15",
"createdAt": "2024-01-01",
"lastLogin": "2026-06-10",
"preferences": { ... },
"roles": [ ... ]
Now your mobile app only needs id, name, and profilePicture to show a user card. But you are
downloading the entire object — phone, address, DOB, preferences, roles — all of it. Wasted
bandwidth. Slower response. Worse performance on mobile.
Partial response lets the client say — "give me only the fields I need."
You go to a buffet. The waiter does not decide what goes on your plate and bring you
everything available. You walk along and pick only what you want. Partial response is the
same — the client picks only the fields it needs instead of receiving everything.
How it works
The client sends a fields query parameter listing the fields it wants —
GET /users/5?fields=id,name,profilePicture
"id": 5,
"name": "Shakib",
"profilePicture": "[Link]
Same endpoint, same URL base, different response shape based on what the client asked for.
Google APIs — almost every Google API supports ?fields= parameter. Google Drive API, Gmail
API, YouTube Data API — all of them.
GET [Link]
Returns only id, name, size for each file — not the 30+ fields available.
GET /me?fields=id,name,email
This pattern is so common it has a name — sparse fieldsets. The JSON:API specification
formalizes it as ?fields[resource]=field1,field2.
In your E-Commerce microservices project — Order Service needs user data to show on an
order. But it only needs userId, name, and email. It does not need address, preferences, roles.
Without partial response — Order Service calls GET /users/5 and downloads everything.
Wasteful network call between services.
There is no built-in Spring annotation for this. You implement it manually in the service or
controller layer.
@GetMapping("/users/{id}")
if (fields == null) {
return [Link]([Link](user));
.collect([Link]([Link]::getKey, [Link]::getValue));
return [Link](filtered);
Jackson has a built-in @JsonFilter mechanism that lets you dynamically include or exclude
fields during serialization. More powerful but more setup.
java
@JsonFilter("userFilter")
Then in the controller you build a FilterProvider based on the requested fields and apply it to
the ObjectMapper. Spring Boot handles the rest.
For interviews — knowing Approach 1 conceptually is enough. Very few interviewers go deep
into Jackson filter implementation at fresher level.
GET /users/5?fields=id,name,[Link],[Link]
Returns —
json
"id": 5,
"name": "Shakib",
"address": {
"city": "Hyderabad",
"pincode": "500001"
This is more complex to implement and usually only needed in large APIs with deeply nested
responses.
A common interview follow-up — why not just create separate DTOs for each use case?
Three things together make a REST API performant on mobile and slow networks —
One — Pagination, so you don't return 1000 records. Two — Compression, so responses are
smaller. Three — Partial response, so unnecessary fields are not transferred.
Knowing all three together and connecting them in one answer makes a strong impression.
One line answer for interview — Partial response allows clients to request only specific fields
using a query parameter like ?fields=id,name,email. The server filters the response to include
only those fields. This reduces payload size, improves performance on mobile networks, and
reduces bandwidth in inter-service communication. Used extensively by Google and
Facebook APIs.
Almost every fresher in an interview says — REST API uses HTTP. That is correct. But when asked is REST
the same as HTTP? or can you build a REST API without HTTP? — most go blank or say no.
This is the concept that separates someone who has just used REST from someone who actually
understands it.
HTTP — is a communication protocol. It defines how data is transferred between client and server over a
network. It specifies the format of requests and responses — methods, headers, status codes, body. It is a
set of rules for how to talk.
REST — is an architectural style. A set of design constraints and principles for building networked
applications. It was defined by Roy Fielding in his PhD dissertation in 2000. REST tells you how to design
your system — not how to transfer data.
HTTP is like the road system — lanes, traffic signals, speed limits, rules of driving. It defines how vehicles
move.
REST is like urban planning principles — roads should connect to destinations logically, intersections
should be clearly marked, one-way streets should make sense. These are design guidelines for how the city
is laid out.
You can have roads without good urban planning. You can have good urban planning on any type of road.
They are separate concerns.
This is the key insight. REST is a set of constraints. Those constraints can be applied over any protocol —
HTTP, HTTPS, CoAP (used in IoT devices), even AMQP in theory.
HTTP happens to map beautifully to REST constraints which is why they are almost always used together.
But REST does not require HTTP.
Similarly — HTTP does not require REST. You can use HTTP in a completely non-RESTful way. SOAP uses
HTTP but is not REST. GraphQL uses HTTP but is not REST.
Roy Fielding defined 6 constraints. If your API follows these — it is RESTful. If it does not — it is just an HTTP
API, not a REST API.
Client and server are separate. Client handles UI. Server handles data and business logic. They
communicate only through the interface. Neither knows about the internal implementation of the other.
Why it matters — you can completely rewrite your Spring Boot backend and the React frontend does not
change, as long as the API contract stays the same.
Constraint 2 — Statelessness
Every request from client to server must contain all the information needed to understand and process it.
The server stores no session state between requests.
Why it matters — this is why JWT exists. The token carries all user information in every request. The server
does not remember the previous request.
Constraint 3 — Cacheability
Responses must define themselves as cacheable or non-cacheable. If cacheable, the client or intermediary
can reuse the response for equivalent future requests.
Why it matters — Cache-Control headers, ETags, Last-Modified headers all exist because of this constraint.
Resource identification in requests — use URIs to identify resources. /users/5 not /getUser?id=5.
HATEOAS — responses include links to related actions. This is Level 3 of Richardson Maturity Model which
you already know.
Client does not know if it is connected directly to the server or through intermediaries — load balancer, API
gateway, cache server, security layer. Each layer only knows about the layer immediately next to it.
Why it matters — this is why you can put an Nginx reverse proxy, an AWS load balancer, and a Spring Cloud
Gateway in front of your service and the client never knows or cares.
Server can send executable code to the client — JavaScript for example. This is the only optional
constraint. Most REST APIs do not implement this.
Honestly — most real-world APIs including yours are not fully RESTful. They are REST-like or RESTful-ish.
They follow Constraints 1, 2, 3, 4 partially, 5 — but almost none implement full HATEOAS. Roy Fielding
himself has written blog posts criticizing APIs that call themselves REST but do not implement HATEOAS.
In practice the industry accepts Level 2 Richardson APIs as REST APIs even though they technically are not
fully REST. This is fine — knowing the distinction is what matters.
Correct answer — "No. GraphQL uses HTTP as a transport but it does not follow REST constraints. GraphQL
uses a single endpoint POST /graphql for all operations — which violates the uniform interface constraint of
REST. It is its own query language and architectural approach, not REST.
One line answer for interview — HTTP is a communication protocol that defines how data is transferred —
methods, headers, status codes. REST is an architectural style defined by 6 constraints — statelessness,
client-server separation, uniform interface, cacheability, layered system, and optional code on demand.
REST most commonly uses HTTP but is not tied to it. SOAP and GraphQL also use HTTP but are not REST
because they do not follow REST constraints.
The server does not remember anything about the client between requests. Every single
request must carry all the information the server needs to process it. The server treats every
request as if it is seeing that client for the very first time.
Stateful — you visit a doctor. The doctor remembers you from last time. You say the pain is
still there. The doctor knows which pain you mean, checks your previous prescription,
continues from where you left off. The doctor holds your context in memory.
Stateless — you call a hospital helpline. Every time you call you get a different operator. They
have no memory of your previous call. Every time you must say your full name, date of birth,
symptoms, previous medications — everything from scratch. The operator does not store
your context between calls.
Server created a session object in memory — { userId: 5, role: ADMIN, cart: [...] }. Server gave
you a JSESSIONID cookie. Every subsequent request sent that cookie. Server looked up your
session in memory using that ID.
This worked fine for one server. But in modern systems you have multiple server instances
behind a load balancer. Request 1 goes to Server A — session stored there. Request 2 goes to
Server B — Server B has no session. You are suddenly logged out.
Solutions like sticky sessions and session replication were hacks. Messy, hard to scale.
With stateless REST — the server stores nothing. The client sends everything the server needs
in every request.
GET /orders
Authorization: Bearer
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzaGFraWIiLCJyb2xlIjoiVVNFUiIsImV4cCI6MTcwMDAwMH0.
abc123
That token contains — who you are, your role, when it expires. The server does not look up a
session. It decodes the token, extracts the information, processes the request. Done.
Request 1 goes to Server A — works fine. Request 2 goes to Server B — works exactly the same.
Request 3 goes to Server C — same.
No shared state between servers. Any server can handle any request. This is why
statelessness is the foundation of horizontal scaling.
Since the server stores nothing — the client must store everything needed for future
requests.
Authentication state — JWT token stored in localStorage or httpOnly cookie. User preferences
— stored client side or sent with each request. Shopping cart — either in client storage or in a
database (not server memory). Pagination position — client tracks which page it is on and
sends ?page=3 with each request.
Freshers often ask — "if the server stores data in a database, is that not state?"
Session state — temporary, request-specific, in-memory data that tracks where a client is in a
conversation with the server. This is what statelessness prohibits.
Resource state — permanent data stored in a database — users, orders, products. This is not
session state. Every REST server stores resource state. Statelessness does not prohibit this.
The rule is — the server must not store anything in memory between requests that is specific
to one client's ongoing conversation. Database records are not that — they are the actual
resources your API manages.
[Link](session ->
[Link]([Link])
);
This tells Spring — do not create HttpSession, do not store anything in session, do not issue
JSESSIONID cookies. Every request is authenticated independently via JWT. This is exactly
what you implemented in your HMS project.
Statelessness is great for scaling but creates real problems you must know —
You cannot invalidate a JWT before it expires because the server stores nothing. A user logs
out — but their token is still valid until expiry. Someone steals that token — they can use it
until it expires.
Solutions — short expiry times (15 minutes) with refresh tokens, or maintain a token blacklist
in Redis (technically a minor violation of pure statelessness but accepted in practice).
Every request carries the full token. In session-based auth a session ID cookie is just a short
string. A JWT is much larger. On high-traffic APIs this adds up.
Since the server holds no client context it cannot say remind this client about something.
This is why SSE and Webhooks — which you just learned — exist to bridge this gap.
Strong answer — Three options depending on requirements. One — store cart in the database
linked to the user ID, fetched with each request using JWT authentication. Two — store cart in
client-side storage like localStorage and send the full cart in the checkout request. Three — a
hybrid — persist to database for logged-in users, localStorage for guests, merge on login.
Option one is most common in production because it persists across devices and sessions.
Stateful (session-based)
One line answer for interview — Statelessness means the server stores no client session
between requests. Every request must carry all information needed to process it —
authentication, context, parameters. JWT implements this by embedding user identity in the
token itself. Statelessness enables horizontal scaling because any server instance can handle
any request without shared session storage.
That completes all 12 concepts you asked for. Here is the full list of what we covered —
The three maybe already knew — ETags (6), Pagination patterns (7), API Gateway (8) — are still
available if you want a clarity session on any of them. Just say the number.