Spring REST API

Build powerful RESTful web services with Spring

🌐 What is Spring REST API?

Spring REST API enables building RESTful web services using Spring MVC. It provides annotations and tools for creating HTTP endpoints, handling requests/responses, and implementing REST architectural principles effortlessly.


// Simple REST controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAll();
    }
    
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.save(product);
    }
}
                                    

REST API Features

🎯

HTTP Methods

Support for GET, POST, PUT, DELETE operations

@GetMapping("/users/{id}")
@PostMapping("/users")
📝

JSON Serialization

Automatic JSON conversion for requests/responses

@RequestBody User user
@ResponseBody
🔍

Path Variables

Extract values from URL paths

@PathVariable Long id
⚠️

Exception Handling

Global error handling and custom responses

@ExceptionHandler
@ControllerAdvice

🔹 Complete REST Controller

Build a full CRUD REST API:

@RestController
@RequestMapping("/api/books")
@CrossOrigin(origins = "http://localhost:3000")
public class BookController {
    
    @Autowired
    private BookService bookService;
    
    // GET /api/books - Get all books
    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        List<Book> books = bookService.findAll();
        return ResponseEntity.ok(books);
    }
    
    // GET /api/books/{id} - Get book by ID
    @GetMapping("/{id}")
    public ResponseEntity<Book> getBookById(@PathVariable Long id) {
        Book book = bookService.findById(id);
        if (book != null) {
            return ResponseEntity.ok(book);
        }
        return ResponseEntity.notFound().build();
    }
    
    // POST /api/books - Create new book
    @PostMapping
    public ResponseEntity<Book> createBook(@Valid @RequestBody Book book) {
        Book savedBook = bookService.save(book);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedBook);
    }
    
    // PUT /api/books/{id} - Update book
    @PutMapping("/{id}")
    public ResponseEntity<Book> updateBook(
            @PathVariable Long id, 
            @Valid @RequestBody Book book) {
        Book updatedBook = bookService.update(id, book);
        if (updatedBook != null) {
            return ResponseEntity.ok(updatedBook);
        }
        return ResponseEntity.notFound().build();
    }
    
    // DELETE /api/books/{id} - Delete book
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        boolean deleted = bookService.deleteById(id);
        if (deleted) {
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.notFound().build();
    }
    
    // GET /api/books/search - Search books
    @GetMapping("/search")
    public ResponseEntity<List<Book>> searchBooks(
            @RequestParam(required = false) String title,
            @RequestParam(required = false) String author) {
        List<Book> books = bookService.search(title, author);
        return ResponseEntity.ok(books);
    }
}

API Endpoints:

GET /api/books → List all books

POST /api/books → Create book

PUT /api/books/1 → Update book

DELETE /api/books/1 → Delete book

🔹 Request/Response Handling

Handle different types of HTTP requests and responses:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    // Request Parameters
    @GetMapping
    public List<User> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String search) {
        return userService.findUsers(page, size, search);
    }
    
    // Path Variables
    @GetMapping("/{userId}/orders/{orderId}")
    public Order getUserOrder(
            @PathVariable Long userId,
            @PathVariable Long orderId) {
        return orderService.findByUserAndId(userId, orderId);
    }
    
    // Request Headers
    @PostMapping
    public ResponseEntity<User> createUser(
            @RequestBody User user,
            @RequestHeader("Authorization") String authHeader) {
        User savedUser = userService.create(user);
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Location", "/api/users/" + savedUser.getId());
        
        return ResponseEntity.status(HttpStatus.CREATED)
                           .headers(headers)
                           .body(savedUser);
    }
    
    // File Upload
    @PostMapping("/{id}/avatar")
    public ResponseEntity<String> uploadAvatar(
            @PathVariable Long id,
            @RequestParam("file") MultipartFile file) {
        try {
            String filename = userService.saveAvatar(id, file);
            return ResponseEntity.ok("Avatar uploaded: " + filename);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                               .body("Upload failed");
        }
    }
}

🔹 Data Validation

Validate request data using Bean Validation:

// Entity with validation annotations
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
    private String username;
    
    @Email(message = "Email should be valid")
    @NotBlank(message = "Email is required")
    private String email;
    
    @NotBlank(message = "Password is required")
    @Size(min = 6, message = "Password must be at least 6 characters")
    private String password;
    
    @Min(value = 18, message = "Age must be at least 18")
    @Max(value = 100, message = "Age must be less than 100")
    private Integer age;
    
    // Getters and setters
}

// Controller with validation
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<?> createUser(@Valid @RequestBody User user, 
                                       BindingResult result) {
        if (result.hasErrors()) {
            Map<String, String> errors = new HashMap<>();
            result.getFieldErrors().forEach(error -> 
                errors.put(error.getField(), error.getDefaultMessage())
            );
            return ResponseEntity.badRequest().body(errors);
        }
        
        User savedUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }
}

Validation Error Response:

400 Bad Request

{"username": "Username is required", "email": "Email should be valid"}

🔹 Global Exception Handling

Handle exceptions globally across all controllers:

// Custom exceptions
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

public class BadRequestException extends RuntimeException {
    public BadRequestException(String message) {
        super(message);
    }
}

// Global exception handler
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(
            ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.NOT_FOUND.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(
            BadRequestException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.badRequest().body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        return ResponseEntity.badRequest().body(errors);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "An unexpected error occurred",
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

// Error response class
public class ErrorResponse {
    private int status;
    private String message;
    private long timestamp;
    
    // Constructors, getters, setters
}

🔹 API Documentation with OpenAPI

Document your REST API automatically:

// Add dependency: springdoc-openapi-starter-webmvc-ui

@RestController
@RequestMapping("/api/products")
@Tag(name = "Product", description = "Product management APIs")
public class ProductController {
    
    @Operation(summary = "Get all products", 
               description = "Retrieve a list of all products")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Successfully retrieved products"),
        @ApiResponse(responseCode = "500", description = "Internal server error")
    })
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAll();
    }
    
    @Operation(summary = "Create a new product")
    @PostMapping
    public ResponseEntity<Product> createProduct(
            @Parameter(description = "Product to be created") 
            @RequestBody Product product) {
        Product savedProduct = productService.save(product);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedProduct);
    }
}

// Entity with schema documentation
@Entity
@Schema(description = "Product entity")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(description = "Unique identifier of the product")
    private Long id;
    
    @Schema(description = "Name of the product", example = "iPhone 13")
    private String name;
    
    @Schema(description = "Price of the product", example = "999.99")
    private BigDecimal price;
}

Swagger UI:

Access API documentation at: http://localhost:8080/swagger-ui.html

🧠 Test Your Knowledge

Which annotation is used to create a REST controller in Spring?