Build real REST APIs from scratch. Understand every annotation, integrate with Spring Data JPA, and wire Producer-Consumer apps with RestTemplate — all explained in plain English.
“REST is just agreeing on HOW two computers talk to each other over the internet”
🍕 Real World Analogy
Imagine a restaurant. You (client) sit at a table and order food by telling the waiter (REST API) what you want. The waiter goes to the kitchen (backend/database), gets your order, and brings it back. You never go into the kitchen yourself. The menu is the API documentation — it tells you what you can ask for and how.
🌐 What REST stands for
REpresentational State Transfer — it's an architectural style (not a protocol or library) for building web services that:
Communicate over HTTP
Exchange data as JSON or XML
Are stateless (server remembers nothing between calls)
Use standard URLs + HTTP verbs to define operations
⚡ Why not just use regular Spring MVC?
Regular Spring MVC returns HTML views (JSP pages) to a browser. REST returns raw data (JSON) that any client can consume — mobile apps, other servers, React frontends, Python scripts. That's the key difference.
Simple Difference
Spring MVC → returns String (view name) → renders JSP Spring REST → returns Java Object → auto-converts to JSON
“There are only 5 things you can do to data — REST maps each to an HTTP verb”
📚 Analogy: Google Docs
Think of a shared Google Doc. You can read it (GET), create a new one (POST), replace it entirely (PUT), update just one line (PATCH), or delete it (DELETE). REST uses the exact same idea for any resource.
HTTP Verb
CRUD Operation
URL Example
What it does
GET
Read
/customers or /customers/1
Fetch all customers, or customer with ID 1. Never changes data.
POST
Create
/customers
Create a new customer. Send data in request body as JSON.
PUT
Update (full)
/customers/1
Replace customer 1 entirely with new data. Send complete object.
PATCH
Update (partial)
/customers/1
Update only the fields you send. Email only? Just send email.
DELETE
Delete
/customers/1
Delete customer 1. Usually no body in request.
Idempotent methods: GET, PUT, DELETE can be called multiple times with same result. POST is NOT idempotent — calling it twice creates two records.
03 — The Annotations Toolkit
Every REST Annotation Explained
“Annotations are just instructions you paste on your code to tell Spring what to do”
🎱 Class-Level Annotations
@RestController
Marks this class as a REST endpoint. It's a shortcut for @Controller + @ResponseBody. Every method return value is automatically converted to JSON.
Class-levelRequired
@Controller
Marks a class as Spring MVC controller (returns view names). For REST, prefer @RestController instead. Use @Controller only when returning HTML views.
Class-levelMVC
@RequestMapping("/api/v1")
Defines the base URL prefix for all methods in this controller. All endpoints inside this controller will start with /api/v1/...
Class & Method
@CrossOrigin
Allows requests from different domains (CORS). Without this, a browser on localhost:3000 cannot call your API on localhost:8080.
Class-levelCORS
🏱 Method-Level Mapping Annotations
@GetMapping("/customers")
Maps HTTP GET requests to this method. Used to read/fetch data. Shortcut for @RequestMapping(method = RequestMethod.GET).
GETRead
@PostMapping("/customers")
Maps HTTP POST requests. Used to create new resources. Request body contains the new object as JSON.
POSTCreate
@PutMapping("/customers/{id}")
Maps HTTP PUT requests. Used to fully update an existing resource. Must send complete object in body.
PUTFull Update
@DeleteMapping("/customers/{id}")
Maps HTTP DELETE requests. Used to delete a resource by its ID from the URL path.
DELETERemove
@PatchMapping("/customers/{id}")
Maps HTTP PATCH requests. Used to partially update a resource. Only the fields you send will be changed.
PATCHPartial Update
🎨 Parameter Extraction Annotations
@PathVariable
Extracts a value from the URL path. URL: /customers/42 → @PathVariable int id gives you 42.
URL pathRequired
@RequestParam
Extracts a value from query string. URL: /customers?type=GOLD → @RequestParam String type gives you GOLD.
Query stringOptional
@RequestBody
Takes the JSON from request body and auto-converts it into a Java object using Jackson. Used in POST/PUT methods to receive data.
Body → Java objectPOST/PUT
@RequestHeader
Extracts a value from an HTTP header. Example: reading an Authorization token from Authorization: Bearer <token> header.
HTTP headersAuth
📈 Response Annotations
@ResponseBody
Tells Spring to write the return value directly to the HTTP response body as JSON/XML. Already included in @RestController.
Implicit in @RestController
@ResponseStatus(HttpStatus.CREATED)
Sets the HTTP status code of the response. Without this, Spring returns 200 by default. Use CREATED (201) for POST, etc.
HTTP status201/204/404
⚠ Common Mistake
@Controller alone does NOT return JSON — it looks for a view (JSP). If you use @Controller and want JSON, you must add @ResponseBody on each method. That's why @RestController = @Controller + @ResponseBody exists — it saves you from forgetting.
04 — @RestController Deep Dive
@RestController — The Heart of REST
“This one annotation turns a plain Java class into a full REST API endpoint”
@RestController// 1. This is a REST controller (JSON in/out)@RequestMapping("/api/customers") // 2. Base URL for all methods belowpublic classCustomerController {
@AutowiredprivateCustomerService customerService; // Injected service// GET /api/customers → returns all customers as JSON@GetMappingpublicList<Customer> getAllCustomers() {
return customerService.findAll(); // Spring auto-converts list → JSON array
}
// GET /api/customers/5 → returns customer with id=5@GetMapping("/{id}")
publicCustomergetById(@PathVariableLong id) {
return customerService.findById(id);
}
// GET /api/customers?type=GOLD → filter by type@GetMapping("/by-type")
publicResponseEntity<List<Customer>> getByType(
@RequestParamString type) {
List<Customer> result = customerService.findByType(type);
returnResponseEntity.ok(result); // 200 OK + JSON body
}
// POST /api/customers → create new customer@PostMappingpublicResponseEntity<Customer> createCustomer(
@RequestBodyCustomer customer) { // JSON body → Customer objectCustomer saved = customerService.save(customer);
returnResponseEntity.status(HttpStatus.CREATED).body(saved); // 201
}
// PUT /api/customers/5 → full update@PutMapping("/{id}")
publicCustomerupdateCustomer(@PathVariableLong id,
@RequestBodyCustomer customer) {
customer.setId(id);
return customerService.save(customer);
}
// DELETE /api/customers/5 → delete@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // 204 - success, no bodypublic voiddeleteCustomer(@PathVariableLong id) {
customerService.deleteById(id);
}
}
05 — URL Design
Designing URLs the Right Way
“Good REST URLs read like sentences describing a resource, not an action”
✓ Good REST URL Design
/customers — all customers
/customers/5 — customer with id 5
/customers/5/orders — orders of customer 5
/products?category=electronics — filtered
Use nouns, not verbs in URLs
Use plural for collections
Use lowercase + hyphens
✕ Bad URL Design (Anti-patterns)
/getCustomer?id=5 — don't use verbs in URL
/deleteCustomer/5 — HTTP verb already says DELETE
/Customer — don't use Pascal Case
/get_all_orders — don't use underscores
/customer — use plural for collections
// PathVariable vs RequestParam — when to use which?// Use @PathVariable when the value IDENTIFIES a specific resource@GetMapping("/customers/{id}") // /customers/42publicCustomergetById(@PathVariableLong id) { ... }
// Use @RequestParam when the value FILTERS or SORTS a collection@GetMapping("/customers") // /customers?type=GOLD&page=0publicList<Customer> filter(
@RequestParam("type") String type,
@RequestParam(defaultValue = "0") int page) { ... }
06 — ResponseEntity
ResponseEntity — Full Control Over Your Response
“ResponseEntity lets you control not just the body, but the status code and headers too”
📦 Analogy: Sending a Package
If you just return a Java object, it's like handing someone a box with no label. ResponseEntity is like packing it properly — you choose the box contents (body), the shipping label (headers), and the delivery status (HTTP status code).
// Without ResponseEntity — limited control@GetMapping("/{id}")
publicCustomerget(@PathVariableLong id) {
return repo.findById(id).orElse(null); // returns null = 200 OK with null body ❌
}
// With ResponseEntity — proper control@GetMapping("/{id}")
publicResponseEntity<Customer> get(@PathVariableLong id) {
return repo.findById(id)
.map(c -> ResponseEntity.ok(c)) // Found → 200 OK + body ✅
.orElse(ResponseEntity.notFound().build()); // Not found → 404 ✅
}
// POST with 201 Created + Location header@PostMappingpublicResponseEntity<Customer> create(@RequestBodyCustomer c) {
Customer saved = repo.save(c);
URI location = URI.create("/api/customers/" + saved.getId());
returnResponseEntity.created(location).body(saved); // 201 Created ✅
}
// Common ResponseEntity shortcuts:ResponseEntity.ok(body) // 200 OKResponseEntity.created(uri).body(data) // 201 CreatedResponseEntity.noContent().build() // 204 No ContentResponseEntity.notFound().build() // 404 Not FoundResponseEntity.badRequest().body(msg) // 400 Bad Request
07 — Spring Data JPA Integration
Connecting REST to a Database via Spring Data JPA
“JPA handles the database side, REST handles the web side. Together they build a full backend.”
🏢 Analogy: Hotel System
The REST Controller is the front desk (handles guest requests). The Service is the manager (applies business rules). The JPA Repository is the filing cabinet that stores/retrieves booking records from the actual database.
// ─── Layer 2: DAO Interface (Spring Data JPA) ───public interfaceCustomerDAOextendsJpaRepository<CustomerBean, Long> {
// Spring auto-generates: SELECT * FROM customers WHERE customer_type = ?List<CustomerBean> findByCustomerType(String customerType);
// Custom JPQL query for complex needs@Query("SELECT c FROM CustomerBean c WHERE c.customerType = :type ORDER BY c.name")
List<CustomerBean> findByTypeSorted(@Param("type") String type);
}
Business logic, validation, @Transactional, orchestrates DAO calls
↓
📚
→ @Repository / JpaRepository (CustomerDAO)
Spring Data auto-implements CRUD. findBy methods generate SQL automatically.
↓
📈
→ Database (MySQL / H2 / PostgreSQL)
Hibernate executes SQL. JPA entities map to table rows.
Rule of Thumb: Controller talks only to Service. Service talks only to DAO. DAO talks only to DB. Never skip a layer — no @Autowired DAO inside a Controller.
09 — RestTemplate
RestTemplate — Your Java HTTP Client
“RestTemplate lets one Spring app call another app's REST API, just like your browser calls websites”
📞 Analogy: Calling a Restaurant
Your app is the customer. The other app's REST API is a restaurant. RestTemplate is your phone. You call (make HTTP request), they answer (return JSON), and you get the food (Java object) delivered without leaving your app.
What RestTemplate Does
Makes HTTP calls from Java code to external REST APIs
Auto-converts JSON response to Java objects
Auto-converts Java objects to JSON for request body
Handles error responses (4xx, 5xx) with exceptions
Supports all HTTP verbs: GET, POST, PUT, DELETE
Core RestTemplate Methods
Method
HTTP Verb
getForObject(url, Type.class)
GET
getForEntity(url, Type.class)
GET + status
postForObject(url, body, Type.class)
POST
put(url, body)
PUT
delete(url)
DELETE
exchange(url, method, entity, Type)
Any verb
// ─── Setting up RestTemplate ───@Configurationpublic classAppConfig {
@BeanpublicRestTemplaterestTemplate() {
return newRestTemplate(); // Create once, inject everywhere
}
}
// ─── Using RestTemplate in a Consumer app ───@Servicepublic classCustomerConsumerService {
@AutowiredprivateRestTemplate restTemplate;
privatestatic finalString BASE_URL = "http://localhost:8080/api/customers";
// 1. GET — fetch a single customerpublicCustomergetCustomerById(Long id) {
String url = BASE_URL + "/" + id;
return restTemplate.getForObject(url, Customer.class);
// Makes GET http://localhost:8080/api/customers/1// JSON response → Customer Java object (auto-deserialised)
}
// 2. GET — fetch a listpublicList<Customer> getByType(String type) {
String url = BASE_URL + "/by-type?customerType=" + type;
Customer[] arr = restTemplate.getForObject(url, Customer[].class);
returnArrays.asList(arr);
}
// 3. POST — create a new customerpublicCustomercreateCustomer(Customer customer) {
return restTemplate.postForObject(BASE_URL, customer, Customer.class);
// customer object → JSON body, response JSON → Customer object
}
// 4. PUT — update an existing customerpublic voidupdateCustomer(Long id, Customer customer) {
String url = BASE_URL + "/" + id;
restTemplate.put(url, customer); // no return value
}
// 5. DELETE — delete a customerpublic voiddeleteCustomer(Long id) {
restTemplate.delete(BASE_URL + "/" + id);
}
// 6. exchange() — most flexible, gives you full ResponseEntitypublicResponseEntity<Customer> getWithHeaders(Long id) {
HttpHeaders headers = newHttpHeaders();
headers.set("Authorization", "Bearer mytoken");
HttpEntity<?> entity = newHttpEntity<>(headers);
return restTemplate.exchange(
BASE_URL + "/" + id,
HttpMethod.GET,
entity,
Customer.class
);
}
}
getForObject vs getForEntity:getForObject returns only the body. getForEntity returns body + HTTP status + headers (a full ResponseEntity). Use getForEntity when you care about the status code.
10 — Producer & Consumer Pattern
Producer — Consumer Architecture with RestTemplate
“Two separate Spring Boot apps. One offers data (Producer). One consumes it (Consumer).”
📺 Analogy: YouTube
YouTube is the Producer — it has a REST API that offers videos. Your phone YouTube app is the Consumer — it calls YouTube's REST API to get videos and shows them. Both are separate apps. They talk via HTTP.
🖥
Consumer App
Console / another service Uses RestTemplate to make HTTP calls
⇆
RestTemplate HTTP/JSON
🌐
Producer App
Spring Boot REST API @RestController Exposes JSON endpoints
// ════════════════════════════════════════════════════════
// PRODUCER APP (runs on port 8080)
// ════════════════════════════════════════════════════════@SpringBootApplicationpublic classCustomerProducerApp {
public static voidmain(String[] args) {
SpringApplication.run(CustomerProducerApp.class, args);
}
}
@RestController@RequestMapping("/api/customers")
public classCustomerController {
@AutowiredprivateCustomerService service;
@GetMapping("/by-type")
publicResponseEntity<List<CustomerBean>> getByType(
@RequestParamString customerType) {
returnResponseEntity.ok(service.findByCustomerType(customerType));
}
}
// ════════════════════════════════════════════════════════
// CONSUMER APP (runs on port 9090) — console application
// ════════════════════════════════════════════════════════@SpringBootApplicationpublic classCustomerConsumerAppimplementsCommandLineRunner {
@AutowiredprivateRestTemplate restTemplate;
public static voidmain(String[] args) {
SpringApplication.run(CustomerConsumerApp.class, args);
}
@Overridepublic voidrun(String... args) {
// Call Producer's REST API to get GOLD customersString url = "http://localhost:8080/api/customers/by-type?customerType=GOLD";
CustomerBean[] customers = restTemplate.getForObject(url, CustomerBean[].class);
for (CustomerBean c : customers) {
System.out.println("Customer: " + c.getName() + " | Type: " + c.getCustomerType());
}
}
}
# Consumer application.properties
server.port=9090 # Different port from producer!
# No datasource needed — consumer hits REST, not DB
Key Points: Producer and Consumer are separate Maven/Spring Boot projects. Producer runs on port 8080, Consumer on 9090. Consumer uses RestTemplate to call Producer over HTTP. Producer doesn't know who calls it — it just responds to REST requests.
⚠ RestTemplate in newer Spring
In Spring 5+, RestTemplate is in maintenance mode. Modern replacement is WebClient (reactive) or RestClient (Spring 6.1+). For assignments and interviews in older codebases, RestTemplate is still valid and commonly tested.
11 — HTTP Status Codes
HTTP Status Codes You Must Know
“The server uses these to tell the client what happened”
200 OK
GET success. Data returned in body.
201 Created
POST success. New resource created. Include Location header.
204 No Content
DELETE/PUT success. No body to return.
400 Bad Request
Client sent invalid data (missing fields, bad JSON, validation failed).
401 Unauthorized
Not authenticated. Token missing or expired.
403 Forbidden
Authenticated but not allowed. No permission for this resource.
404 Not Found
Resource does not exist. Customer with id 999 not found.
409 Conflict
Duplicate entry. Email already registered.
500 Internal Error
Server crashed. Unhandled exception in your code.
12 — Interview Q&A
Top Interview Questions
“The exact questions interviewers ask about Spring REST”
What is the difference between @Controller and @RestController?
@Controller returns view names (Strings like "login") which Spring resolves to JSP/Thymeleaf templates. @RestController = @Controller + @ResponseBody on every method. Every return value is written directly to the HTTP response body as JSON/XML — no view resolution happens. Use @RestController for REST APIs.
What is @PathVariable vs @RequestParam?
@PathVariable extracts from URL path: GET /customers/42 → @PathVariable Long id = 42. Use to identify a specific resource. @RequestParam extracts from query string: GET /customers?type=GOLD → @RequestParam String type = "GOLD". Use for filters, sorting, pagination.
What does @RequestBody do?
Tells Spring to read the HTTP request body (JSON) and deserialise it into a Java object using Jackson. Used in POST/PUT methods. Spring reads Content-Type: application/json header and uses the appropriate MessageConverter.
Why use ResponseEntity instead of returning the object directly?
Returning an object gives 200 always. ResponseEntity lets you control the HTTP status code (201 for created, 404 for missing), add headers like Location, and return different body types based on conditions. Proper REST APIs must return correct status codes.
What is RestTemplate and when do you use it?
RestTemplate is Spring's synchronous HTTP client. Use it in consumer applications that call external REST APIs. It handles JSON serialisation/deserialisation automatically. Core methods: getForObject, getForEntity, postForObject, put, delete, exchange.
Difference between getForObject and getForEntity?
getForObject returns only the response body deserialised to the specified type. getForEntity returns a ResponseEntity wrapping the body + HTTP status + headers. Use getForEntity when you need to check the status code or read response headers.
What is the Producer-Consumer pattern?
Producer is a Spring Boot app with @RestController that exposes REST endpoints returning JSON. Consumer is another app that uses RestTemplate to call the Producer's REST API. They run as separate processes, communicate over HTTP, and are fully decoupled. No shared code or database between them.
How does Spring REST integrate with JPA?
@RestController receives HTTP requests and returns Java objects (Jackson converts to JSON automatically). @Service handles business logic and calls @Repository. JpaRepository talks to the database via Hibernate. Spring Boot auto-configures Jackson when you add spring-boot-starter-web, so Java objects ↔ JSON conversion is seamless.
What HTTP status should a DELETE method return?
204 No Content on success (resource deleted, nothing to return). 404 Not Found if the resource didn't exist. Use @ResponseStatus(HttpStatus.NO_CONTENT) on the method, or return ResponseEntity.noContent().build().
What is @CrossOrigin?
CORS is a browser security policy blocking JavaScript from calling APIs on a different domain/port. @CrossOrigin tells Spring to include CORS headers in the response, allowing specific origins. Without it, a React app on localhost:3000 cannot call your API on localhost:8080.