Java Academy

Master Java Development

Menu

Core Java

Servlets

JDBC

Microservices

Build Tools

APIs

APIs · Lesson 1

Introduction to REST

What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. Instead of RPC (Remote Procedure Call) style APIs where you call specific methods, REST treats everything as resources (users, products, orders) accessed via URLs using HTTP methods (GET, POST, PUT, DELETE). REST is stateless — each request contains all information needed; the server doesn't maintain request state. REST has become the standard for web APIs because it's simple, scalable, and aligns naturally with HTTP.

REST Principles and HTTP Methods

RESTful APIs follow key principles: resources (identified by URLs), standard methods (HTTP verbs), stateless communication, representations (JSON, XML). Each HTTP method has a semantic meaning: GET retrieves, POST creates, PUT updates, DELETE removes.

REST Principles:
1. Client-Server: Separate concerns (client UI, server data/business logic)
2. Stateless: Server doesn't store client state; each request is independent
3. Cacheable: Responses can be cached (GET requests)
4. Uniform Interface: Consistent API design
5. Layered System: Clients can't tell if connected directly to end server
6. Code on Demand (optional): Server can extend client functionality (JavaScript)
HTTP Methods and Semantics:

GET /api/users             → Retrieve all users (safe, idempotent)
GET /api/users/123         → Retrieve user with ID 123
POST /api/users            → Create new user (creates new resource)
PUT /api/users/123         → Update entire user 123
PATCH /api/users/123       → Partially update user 123
DELETE /api/users/123      → Delete user 123

Safe: Doesn't modify server state (GET, HEAD, OPTIONS)
Idempotent: Multiple calls have same effect as one call (GET, PUT, DELETE)
// RESTful API example in Spring Boot
@RestController
@RequestMapping("/api/users")
public class UserController {
    private Map<Integer, User> users = new HashMap<>();
    
    // GET /api/users - Retrieve all users
    @GetMapping
    public List<User> getAllUsers() {
        return new ArrayList<>(users.values());
    }
    
    // GET /api/users/123 - Retrieve user by ID
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable int id) {
        User user = users.get(id);
        return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
    }
    
    // POST /api/users - Create new user
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        int id = users.size() + 1;
        user.setId(id);
        users.put(id, user);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
    
    // PUT /api/users/123 - Update entire user
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable int id, @RequestBody User updatedUser) {
        if (!users.containsKey(id)) {
            return ResponseEntity.notFound().build();
        }
        updatedUser.setId(id);
        users.put(id, updatedUser);
        return ResponseEntity.ok(updatedUser);
    }
    
    // DELETE /api/users/123 - Delete user
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable int id) {
        if (!users.containsKey(id)) {
            return ResponseEntity.notFound().build();
        }
        users.remove(id);
        return ResponseEntity.noContent().build();  // 204 No Content
    }
}

Resource Naming and URL Design

RESTful URLs use nouns (resources), not verbs (actions). URLs should be predictable and hierarchical. Use plural nouns for collections.

Bad (not RESTful - uses verbs):
GET /api/getUsers
POST /api/createUser
PUT /api/updateUser/123
DELETE /api/deleteUser/123

Good (RESTful - uses nouns and HTTP methods):
GET /api/users
POST /api/users
PUT /api/users/123
DELETE /api/users/123

Nested resources:
GET /api/users/123/orders              → Get all orders for user 123
GET /api/users/123/orders/456          → Get specific order
POST /api/users/123/orders             → Create order for user 123

Query parameters for filtering/pagination:
GET /api/users?age=25                  → Filter by age
GET /api/users?page=2&limit=10         → Pagination
GET /api/users?sort=name&order=asc     → Sorting
GET /api/products?category=electronics&price_max=1000  → Multiple filters

Content Negotiation: JSON and beyond

REST APIs typically use JSON as the default format, but can support XML, HAL JSON, or others. Clients indicate preferred format via Accept header; servers respond with appropriate Content-Type.

class User {
    @JsonProperty("user_id")  // Customize JSON field name
    private int id;
    
    @JsonIgnore               // Exclude from JSON
    private String password;
    
    @JsonFormat(pattern = "yyyy-MM-dd")  // Date formatting
    private java.util.Date createdAt;
    
    // ... getters/setters
}

// Spring Boot automatically converts User to JSON:
// {"user_id": 123, "name": "Alice", "email": "alice@example.com", "createdAt": "2023-01-15"}

@RestController
@RequestMapping("/api/users")
public class UserController {
    // Explicitly specify content type
    @GetMapping(produces = "application/json")
    public User getUser() {
        return new User(1, "Alice");
    }
    
    // Support multiple content types
    @GetMapping(produces = {"application/json", "application/xml"})
    public User getUserMultiFormat() {
        return new User(1, "Alice");
    }
    
    // Client sends JSON, server responds with JSON
    @PostMapping(consumes = "application/json", produces = "application/json")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // ... create user
        return ResponseEntity.status(201).body(user);
    }
}

Pagination and Filtering

For large datasets, APIs must support pagination (return data in pages) and filtering (narrow down results).

@RestController
@RequestMapping("/api/products")
public class ProductController {
    private List<Product> products = loadProducts();
    
    // Pagination: GET /api/products?page=2&size=10
    @GetMapping
    public ResponseEntity<Page<Product>> getProducts(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        
        int startIndex = (page - 1) * size;
        int endIndex = Math.min(startIndex + size, products.size());
        
        List<Product> pageContent = products.subList(startIndex, endIndex);
        Page<Product> result = new Page<>(pageContent, page, size, products.size());
        
        return ResponseEntity.ok(result);
    }
    
    // Filtering and sorting: GET /api/products?category=electronics&minPrice=100&sort=price&order=asc
    @GetMapping("/search")
    public List<Product> searchProducts(
            @RequestParam(required = false) String category,
            @RequestParam(required = false) Double minPrice,
            @RequestParam(defaultValue = "name") String sort,
            @RequestParam(defaultValue = "asc") String order) {
        
        List<Product> filtered = products.stream()
            .filter(p -> category == null || p.getCategory().equals(category))
            .filter(p -> minPrice == null || p.getPrice() >= minPrice)
            .collect(java.util.stream.Collectors.toList());
        
        // Sort
        if ("price".equals(sort)) {
            filtered.sort((a, b) -> Double.compare(a.getPrice(), b.getPrice()));
        } else {
            filtered.sort((a, b) -> a.getName().compareTo(b.getName()));
        }
        
        if ("desc".equals(order)) {
            java.util.Collections.reverse(filtered);
        }
        
        return filtered;
    }
}

class Page<T> {
    private List<T> content;
    private int currentPage;
    private int pageSize;
    private int totalItems;
    private int totalPages;
    
    Page(List<T> content, int currentPage, int pageSize, int totalItems) {
        this.content = content;
        this.currentPage = currentPage;
        this.pageSize = pageSize;
        this.totalItems = totalItems;
        this.totalPages = (totalItems + pageSize - 1) / pageSize;
    }
    
    // Getters...
    public List<T> getContent() { return content; }
    public int getCurrentPage() { return currentPage; }
    public int getTotalPages() { return totalPages; }
}
REST Best PracticeExample
Use nouns for resources/api/users, /api/orders
Use plural for collections/api/users not /api/user
Use HTTP methods for actionsPOST to create, PUT to update, DELETE to delete
Return appropriate status codes201 for creation, 404 for not found, 400 for bad request
Support pagination?page=1&size=10
Support filtering?category=electronics&minPrice=100
Use JSON as default formatLightweight, human-readable
Version APIs/api/v1/users, /api/v2/users

Common Mistake: Using verbs in URLs. /api/getUsers, /api/createOrder violates REST principles. HTTP methods are the verbs; URLs should be resources. Also, returning 200 OK for all responses (even errors) hides failures from clients. Use status codes: 2xx for success, 4xx for client errors, 5xx for server errors.

Best Practices for REST APIs

  • Use meaningful resource names (nouns, plural)
  • Return appropriate HTTP status codes (201 for creation, 404 for not found, etc.)
  • Support pagination for large result sets
  • Support filtering and sorting for flexible queries
  • Version your API (/api/v1/, /api/v2/)
  • Use consistent error responses with error codes and messages
  • Implement rate limiting to prevent abuse
  • Use HTTPS for security in production
  • Document endpoints with Swagger/OpenAPI
  • Support content negotiation (JSON, XML if needed)

Real-World Example: E-Commerce API

An e-commerce REST API has endpoints: GET /api/v1/products (list products with pagination), GET /api/v1/products/{id} (product details), POST /api/v1/orders (create order), GET /api/v1/orders/{id} (order status), DELETE /api/v1/orders/{id} (cancel order). Filtering works: GET /api/v1/products?category=electronics&minPrice=100&maxPrice=500. Pagination: GET /api/v1/products?page=2&size=20. On POST /orders with valid input, it returns 201 Created with Location header. On invalid input, it returns 400 Bad Request with error details. By following REST principles, the API is predictable, scalable, and easy for mobile apps and web frontends to consume.

Quiz Check

Quick Knowledge Test

Loading quiz questions...