⌂ Home
Spring REST · API Design & Integration

Spring REST — Layman Concepts

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 Architecture All Annotations JPA Integration RestTemplate Producer & Consumer HTTP Methods Interview Q&A
HTTP Request DispatcherServlet @RestController @Service JPA Repository JSON Response

What is REST? Why does it exist?

“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

Key REST Constraints: Stateless (no session), Uniform interface (URLs + HTTP verbs), Client-Server separation, Cacheable responses, Layered system.

HTTP Methods = CRUD Operations

“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 VerbCRUD OperationURL ExampleWhat 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.

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.

@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 below public class CustomerController { @Autowired private CustomerService customerService; // Injected service // GET /api/customers → returns all customers as JSON @GetMapping public List<Customer> getAllCustomers() { return customerService.findAll(); // Spring auto-converts list → JSON array } // GET /api/customers/5 → returns customer with id=5 @GetMapping("/{id}") public Customer getById(@PathVariable Long id) { return customerService.findById(id); } // GET /api/customers?type=GOLD → filter by type @GetMapping("/by-type") public ResponseEntity<List<Customer>> getByType( @RequestParam String type) { List<Customer> result = customerService.findByType(type); return ResponseEntity.ok(result); // 200 OK + JSON body } // POST /api/customers → create new customer @PostMapping public ResponseEntity<Customer> createCustomer( @RequestBody Customer customer) { // JSON body → Customer object Customer saved = customerService.save(customer); return ResponseEntity.status(HttpStatus.CREATED).body(saved); // 201 } // PUT /api/customers/5 → full update @PutMapping("/{id}") public Customer updateCustomer(@PathVariable Long id, @RequestBody Customer customer) { customer.setId(id); return customerService.save(customer); } // DELETE /api/customers/5 → delete @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) // 204 - success, no body public void deleteCustomer(@PathVariable Long id) { customerService.deleteById(id); } }

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/42 public Customer getById(@PathVariable Long id) { ... } // Use @RequestParam when the value FILTERS or SORTS a collection @GetMapping("/customers") // /customers?type=GOLD&page=0 public List<Customer> filter( @RequestParam("type") String type, @RequestParam(defaultValue = "0") int page) { ... }

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}") public Customer get(@PathVariable Long id) { return repo.findById(id).orElse(null); // returns null = 200 OK with null body ❌ } // With ResponseEntity — proper control @GetMapping("/{id}") public ResponseEntity<Customer> get(@PathVariable Long 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 @PostMapping public ResponseEntity<Customer> create(@RequestBody Customer c) { Customer saved = repo.save(c); URI location = URI.create("/api/customers/" + saved.getId()); return ResponseEntity.created(location).body(saved); // 201 Created ✅ } // Common ResponseEntity shortcuts: ResponseEntity.ok(body) // 200 OK ResponseEntity.created(uri).body(data) // 201 Created ResponseEntity.noContent().build() // 204 No Content ResponseEntity.notFound().build() // 404 Not Found ResponseEntity.badRequest().body(msg) // 400 Bad Request

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 by Layer — Full CustomerBean Example

// ─── Layer 1: Entity (The Java ↔ Database mapping) ─── @Entity @Table(name = "customers") public class CustomerBean { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "customer_type", nullable = false) private String customerType; // e.g. "GOLD", "SILVER", "PLATINUM" private String name; private String email; // getters & setters... }
// ─── Layer 2: DAO Interface (Spring Data JPA) ─── public interface CustomerDAO extends JpaRepository<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); }
// ─── Layer 3: Service Interface ─── public interface CustomerService { List<CustomerBean> findByCustomerType(String customerType); CustomerBean save(CustomerBean customer); CustomerBean findById(Long id); void deleteById(Long id); } // ─── Layer 3b: Service Implementation ─── @Service public class CustomerServiceImpl implements CustomerService { @Autowired private CustomerDAO customerDAO; // JPA Repository injected here @Override public List<CustomerBean> findByCustomerType(String customerType) { return customerDAO.findByCustomerType(customerType); // hits DB } @Override @Transactional public CustomerBean save(CustomerBean customer) { return customerDAO.save(customer); } }
// ─── Layer 4: REST Controller (wires everything together) ─── @RestController @RequestMapping("/api/customers") public class CustomerController { @Autowired private CustomerService customerService; // Assignment: getCustomerByCustomerType @GetMapping("/by-type") public ResponseEntity<List<CustomerBean>> getByType( @RequestParam("customerType") String customerType) { List<CustomerBean> customers = customerService.findByCustomerType(customerType); if (customers.isEmpty()) { return ResponseEntity.notFound().build(); // 404 } return ResponseEntity.ok(customers); // 200 OK + JSON list } }
⚠ Important: application.properties for JPA + REST

You must configure your DB connection for the JPA layer to work:

# application.properties spring.datasource.url=jdbc:mysql://localhost:3306/customerdb spring.datasource.username=root spring.datasource.password=yourpassword spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update # auto-creates/updates tables spring.jpa.show-sql=true # print SQL to console spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect server.port=8080

The Full Spring REST + JPA Architecture

“Every request flows through 4 layers. Each layer has one job.”

🌐
→ @RestController (CustomerController)
Receives HTTP Request, maps URL+verb, returns JSON response
→ @Service (CustomerServiceImpl)
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.

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

MethodHTTP 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 ─── @Configuration public class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); // Create once, inject everywhere } } // ─── Using RestTemplate in a Consumer app ─── @Service public class CustomerConsumerService { @Autowired private RestTemplate restTemplate; private static final String BASE_URL = "http://localhost:8080/api/customers"; // 1. GET — fetch a single customer public Customer getCustomerById(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 list public List<Customer> getByType(String type) { String url = BASE_URL + "/by-type?customerType=" + type; Customer[] arr = restTemplate.getForObject(url, Customer[].class); return Arrays.asList(arr); } // 3. POST — create a new customer public Customer createCustomer(Customer customer) { return restTemplate.postForObject(BASE_URL, customer, Customer.class); // customer object → JSON body, response JSON → Customer object } // 4. PUT — update an existing customer public void updateCustomer(Long id, Customer customer) { String url = BASE_URL + "/" + id; restTemplate.put(url, customer); // no return value } // 5. DELETE — delete a customer public void deleteCustomer(Long id) { restTemplate.delete(BASE_URL + "/" + id); } // 6. exchange() — most flexible, gives you full ResponseEntity public ResponseEntity<Customer> getWithHeaders(Long id) { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer mytoken"); HttpEntity<?> entity = new HttpEntity<>(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.

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) // ════════════════════════════════════════════════════════ @SpringBootApplication public class CustomerProducerApp { public static void main(String[] args) { SpringApplication.run(CustomerProducerApp.class, args); } } @RestController @RequestMapping("/api/customers") public class CustomerController { @Autowired private CustomerService service; @GetMapping("/by-type") public ResponseEntity<List<CustomerBean>> getByType( @RequestParam String customerType) { return ResponseEntity.ok(service.findByCustomerType(customerType)); } }
// ════════════════════════════════════════════════════════ // CONSUMER APP (runs on port 9090) — console application // ════════════════════════════════════════════════════════ @SpringBootApplication public class CustomerConsumerApp implements CommandLineRunner { @Autowired private RestTemplate restTemplate; public static void main(String[] args) { SpringApplication.run(CustomerConsumerApp.class, args); } @Override public void run(String... args) { // Call Producer's REST API to get GOLD customers String 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.

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.

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.

⚡ Memory Cheat Sheet

“Everything in 60 seconds before your interview”

@RestController
= @Controller
+ @ResponseBody
@GetMapping
Read / Fetch data
Idempotent ✅
@PostMapping
Create new resource
NOT idempotent ❌
@PutMapping
Full update
Send complete object
@DeleteMapping
Delete resource
Returns 204
@PathVariable
/items/{id}
Identifies resource
@RequestParam
?type=GOLD
Filters / sorts
@RequestBody
JSON body → Java
POST/PUT methods
ResponseEntity
Control status code
+ headers + body
RestTemplate
Java HTTP client
Consumer app uses this
Producer
@RestController
Exposes JSON API
Consumer
RestTemplate
Calls Producer API
200/201/204
OK / Created / No Content
GET / POST / DELETE
REST Layers
Controller → Service
→ Repository → DB
@CrossOrigin
Allow other domains
to call your API
↑ Back to top