|
| 1 | +# 5.5-REST Exception Handling\README.md |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +Welcome to **5.5 - REST Exception Handling** |
| 6 | + |
| 7 | + If you’re new to coding, this is your guide to handling errors gracefully in REST APIs! We’ll explore **exception handling**—fixing ugly error logs when things go wrong (like a bad student ID from [5.4](#54-path-variables)). We’ll enhance our app with custom errors, using Jackson for JSON, and make it user-friendly. Think of this as turning a server crash into a polite "Not Found" note—building on REST, JSON, and path variables. Let’s dive in! 🚀 |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Table of Contents |
| 12 | + |
| 13 | +1. [What Is REST Exception Handling?](#1-what-is-rest-exception-handling) |
| 14 | + - [1.1 Exception Handling: Fixing Errors](#11-exception-handling-fixing-errors) |
| 15 | + - [1.2 The Problem from `jacksondemo`](#12-the-problem-from-jacksondemo) |
| 16 | + - [1.3 Key Terms for Beginners](#13-key-terms-for-beginners) |
| 17 | +2. [Learning Roadmap](#2-learning-roadmap) |
| 18 | + - [2.1 Why Handle Exceptions](#21-why-handle-exceptions) |
| 19 | + - [2.2 Four Steps to Handle Errors](#22-four-steps-to-handle-errors) |
| 20 | + - [2.3 Building Custom Error Handling](#23-building-custom-error-handling) |
| 21 | +3. [Practical Demonstration](#3-practical-demonstration) |
| 22 | + - [3.1 Understanding the Problem](#31-understanding-the-problem) |
| 23 | + - [3.2 Creating `rest-api-exception-demo`](#32-creating-rest-api-exception-demo) |
| 24 | + - [3.3 Testing with Browser and Postman](#33-testing-with-browser-and-postman) |
| 25 | +4. [Practical Application](#4-practical-application) |
| 26 | + - [4.1 Best Practices](#41-best-practices) |
| 27 | + - [4.2 Common Mistakes to Avoid](#42-common-mistakes-to-avoid) |
| 28 | + - [4.3 Hands-On Exercises](#43-hands-on-exercises) |
| 29 | +5. [Wrapping Up](#5-wrapping-up) |
| 30 | + - [5.1 Resources for Further Learning](#51-resources-for-further-learning) |
| 31 | + - [5.2 Summary of Key Takeaways](#52-summary-of-key-takeaways) |
| 32 | + - [5.3 What’s Next](#53-whats-next) |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## 1. What Is REST Exception Handling? |
| 37 | + |
| 38 | +### 1.1 Exception Handling: Fixing Errors |
| 39 | + |
| 40 | +- **Definition**: Exception handling catches and manages errors in REST APIs, sending nice responses instead of crashes. |
| 41 | +- **Purpose**: Avoids ugly internal server errors—makes apps user-friendly. |
| 42 | + |
| 43 | +#### Real-World Analogy |
| 44 | + |
| 45 | +Think of it as a polite apology— вместо "Crash!" you say, "Sorry, that student isn’t here!" |
| 46 | + |
| 47 | +### 1.2 The Problem from `jacksondemo` |
| 48 | + |
| 49 | +- **Recap**: From [5.4](#54-path-variables), `jacksondemo` lists students at `/api/students` and fetches one via `/api/students/{id}` (0-4). |
| 50 | +- **Issue**: Asking for `/api/students/99` (beyond the list) gives an ugly "Internal Server Error" log—bad for users! |
| 51 | + |
| 52 | +#### Real-World Analogy |
| 53 | + |
| 54 | +It’s like asking for a nonexistent student ID and getting a filing cabinet explosion—let’s clean it up! |
| 55 | + |
| 56 | +### 1.3 Key Terms for Beginners |
| 57 | + |
| 58 | +Your newbie glossary: |
| 59 | + |
| 60 | +| Term | Meaning | Example | |
| 61 | +|-----------------------|----------------------------------------------|----------------------------------| |
| 62 | +| **Exception** | Error that crashes the app | `ArrayIndexOutOfBounds` | |
| 63 | +| **Custom Exception** | Your own error type | `StudentNotFoundException` | |
| 64 | +| **Error Response** | Friendly error message | `{"status": 404, "message": "..."}` | |
| 65 | +| **@ExceptionHandler** | Catches exceptions in controllers | Handles `StudentNotFoundException` | |
| 66 | +| **ResponseEntity** | Wraps HTTP responses | Returns error with status 404 | |
| 67 | +| **Jackson** | Converts Java to JSON | `StudentErrorResponse` → JSON | |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## 2. Learning Roadmap |
| 72 | + |
| 73 | +Your path to error-handling mastery! |
| 74 | + |
| 75 | +### 2.1 Why Handle Exceptions |
| 76 | + |
| 77 | +- **What You’ll Learn**: Why errors need fixing. |
| 78 | +- **Goal**: See the user benefit. |
| 79 | + |
| 80 | +### 2.2 Four Steps to Handle Errors |
| 81 | + |
| 82 | +- **What You’ll Learn**: Steps to create custom error handling. |
| 83 | +- **Goal**: Understand the process. |
| 84 | + |
| 85 | +### 2.3 Building Custom Error Handling |
| 86 | + |
| 87 | +- **What You’ll Do**: Fix `jacksondemo` errors. |
| 88 | +- **Goal**: Send pretty error messages. |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## 3. Practical Demonstration |
| 93 | + |
| 94 | +Let’s fix errors in a new `rest-api-exception-demo` app! |
| 95 | + |
| 96 | +### 3.1 Understanding the Problem |
| 97 | + |
| 98 | +- **The Issue**: |
| 99 | + - From [5.4](#54-path-variables), `/api/students/99` (beyond 0-4) crashes with an "Internal Server Error" (e.g., `ArrayIndexOutOfBoundsException`). |
| 100 | + - Ugly log: Timestamp, status, error, path—not user-friendly. |
| 101 | +- **Goal**: |
| 102 | + - Replace with: `{"status": 404, "message": "Student id not found - 99", "timestamp": 1234567890}`. |
| 103 | +- **Flow**: |
| 104 | + - Client (browser/Postman) sends bad ID → Service checks → Throws exception → Handler catches → Sends nice JSON error. |
| 105 | +- **Why?**: Users deserve clear messages, not server logs! |
| 106 | + |
| 107 | +>[!NOTE] |
| 108 | +>Errors happen—handling them makes your app polite! |
| 109 | +
|
| 110 | +### 3.2 Creating `rest-api-exception-demo` |
| 111 | + |
| 112 | +- **Goal**: Handle bad student IDs with a custom error response. |
| 113 | +- **Steps**: |
| 114 | + 1. **Setup Project**: |
| 115 | + - Copy `jacksondemo` from [5.4](#54-path-variables) → Rename to `rest-api-exception-demo`. |
| 116 | + - Keep `pom.xml` (Spring Web, DevTools), `Student` POJO unchanged. |
| 117 | + 2. **Step 1: Create `StudentErrorResponse` POJO**: |
| 118 | + - In `src/main/java/com.example.restapiexceptiondemo.entity`, add: |
| 119 | + ```java |
| 120 | + package com.example.restapiexceptiondemo.entity; |
| 121 | + |
| 122 | + public class StudentErrorResponse { |
| 123 | + private int status; |
| 124 | + private String message; |
| 125 | + private long timestamp; |
| 126 | + |
| 127 | + // Default constructor |
| 128 | + public StudentErrorResponse() {} |
| 129 | + |
| 130 | + // Parameterized constructor |
| 131 | + public StudentErrorResponse(int status, String message, long timestamp) { |
| 132 | + this.status = status; |
| 133 | + this.message = message; |
| 134 | + this.timestamp = timestamp; |
| 135 | + } |
| 136 | + |
| 137 | + // Getters and Setters |
| 138 | + public int getStatus() { return status; } |
| 139 | + public void setStatus(int status) { this.status = status; } |
| 140 | + public String getMessage() { return message; } |
| 141 | + public void setMessage(String message) { this.message = message; } |
| 142 | + public long getTimestamp() { return timestamp; } |
| 143 | + public void setTimestamp(long timestamp) { this.timestamp = timestamp; } |
| 144 | + } |
| 145 | + ``` |
| 146 | + - Note: Boilerplate—Lombok (later topic) could simplify this. |
| 147 | + 3. **Step 2: Create `StudentNotFoundException`**: |
| 148 | + - In `src/main/java/com.example.restapiexceptiondemo.exception`, create package `exception`, then: |
| 149 | + ```java |
| 150 | + package com.example.restapiexceptiondemo.exception; |
| 151 | + |
| 152 | + public class StudentNotFoundException extends RuntimeException { |
| 153 | + public StudentNotFoundException(String message) { |
| 154 | + super(message); |
| 155 | + } |
| 156 | + } |
| 157 | + ``` |
| 158 | + - Extends `RuntimeException` (or `Exception`), passes message (e.g., "Student id not found - 99") to parent. |
| 159 | + 4. **Step 3: Update `StudentRestController` to Throw Exception**: |
| 160 | + - Edit `src/main/java/com.example.restapiexceptiondemo.controller/StudentRestController.java`: |
| 161 | + ```java |
| 162 | + package com.example.restapiexceptiondemo.controller; |
| 163 | + |
| 164 | + import com.example.restapiexceptiondemo.entity.Student; |
| 165 | + import com.example.restapiexceptiondemo.exception.StudentNotFoundException; |
| 166 | + import jakarta.annotation.PostConstruct; |
| 167 | + import org.springframework.web.bind.annotation.*; |
| 168 | + |
| 169 | + import java.util.ArrayList; |
| 170 | + import java.util.List; |
| 171 | + |
| 172 | + @RestController |
| 173 | + @RequestMapping("/api") |
| 174 | + public class StudentRestController { |
| 175 | + |
| 176 | + private List<Student> students; |
| 177 | + |
| 178 | + @PostConstruct |
| 179 | + public void loadStudents() { |
| 180 | + students = new ArrayList<>(); |
| 181 | + students.add(new Student("Liam", "Neeson")); // 0 |
| 182 | + students.add(new Student("Mario", "Rossi")); // 1 |
| 183 | + students.add(new Student("Ram", "Charan")); // 2 |
| 184 | + students.add(new Student("Amir", "Khan")); // 3 |
| 185 | + students.add(new Student("Bruce", "Willis")); // 4 |
| 186 | + } |
| 187 | + |
| 188 | + @GetMapping("/students") |
| 189 | + public List<Student> getStudents() { |
| 190 | + return students; |
| 191 | + } |
| 192 | + |
| 193 | + @GetMapping("/students/{studentId}") |
| 194 | + public Student getStudent(@PathVariable("studentId") int studentId) { |
| 195 | + if (studentId >= students.size() || studentId < 0) { |
| 196 | + throw new StudentNotFoundException("Student id not found - " + studentId); |
| 197 | + } |
| 198 | + return students.get(studentId); |
| 199 | + } |
| 200 | + } |
| 201 | + ``` |
| 202 | + - Checks if `studentId` is invalid (negative or ≥ list size)—throws exception if so. |
| 203 | + 5. **Step 4: Add Exception Handler**: |
| 204 | + - In same `StudentRestController`, add: |
| 205 | + ```java |
| 206 | + import com.example.restapiexceptiondemo.entity.StudentErrorResponse; |
| 207 | + import org.springframework.http.HttpStatus; |
| 208 | + import org.springframework.http.ResponseEntity; |
| 209 | + |
| 210 | + // ... (inside StudentRestController class) |
| 211 | + |
| 212 | + @ExceptionHandler |
| 213 | + public ResponseEntity<StudentErrorResponse> handleException(StudentNotFoundException exc) { |
| 214 | + StudentErrorResponse error = new StudentErrorResponse(); |
| 215 | + error.setStatus(HttpStatus.NOT_FOUND.value()); // 404 |
| 216 | + error.setMessage(exc.getMessage()); // "Student id not found - 99" |
| 217 | + error.setTimestamp(System.currentTimeMillis()); // Milliseconds |
| 218 | + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); |
| 219 | + } |
| 220 | + ``` |
| 221 | + - `@ExceptionHandler` catches `StudentNotFoundException`, builds `StudentErrorResponse`, wraps in `ResponseEntity`. |
| 222 | + 6. **Run App**: |
| 223 | + - Right-click `RestApiExceptionDemoApplication.java` > `Run As > Spring Boot App`. |
| 224 | + - Console: "Tomcat started on port(s): 8080." |
| 225 | + |
| 226 | +- **What’s Happening**: |
| 227 | + - Bad ID → Throws `StudentNotFoundException` → Handler catches → Returns JSON error (Jackson converts `StudentErrorResponse`). |
| 228 | + |
| 229 | +>[!TIP] |
| 230 | +>`ResponseEntity` is your HTTP wrapper—status, body, all in one! |
| 231 | + |
| 232 | +### 3.3 Testing with Browser and Postman |
| 233 | + |
| 234 | +- **Browser Test**: |
| 235 | + - `localhost:8080/api/students` → JSON array of 5 students. |
| 236 | + - `/api/students/0` → `{"firstName": "Liam", "lastName": "Neeson"}`. |
| 237 | + - `/api/students/99` → `{"status": 404, "message": "Student id not found - 99", "timestamp": 1234567890}`. |
| 238 | + - `/api/students/5` → Same 404 error—clean and clear! |
| 239 | +- **Postman Test**: |
| 240 | + - `GET localhost:8080/api/students` → 5 students, `200 OK`. |
| 241 | + - `/api/students/2` → Third student, `200 OK`. |
| 242 | + - `/api/students/99` → `{"status": 404, "message": "Student id not found - 99", "timestamp": ...}`, `404 Not Found`. |
| 243 | +- **What’s Happening**: |
| 244 | + - No more server errors—custom JSON response instead! |
| 245 | + - DevTools auto-reloads changes. |
| 246 | + |
| 247 | +>[!NOTE] |
| 248 | +>Exceptions handled—users get clarity, not chaos! |
| 249 | + |
| 250 | +--- |
| 251 | + |
| 252 | +## 4. Practical Application |
| 253 | + |
| 254 | +Make it stick! |
| 255 | + |
| 256 | +### 4.1 Best Practices |
| 257 | + |
| 258 | +- **Clear Messages**: "Student id not found - 99"—specific and helpful. |
| 259 | +- **Use 404**: Matches "Not Found" status—standard REST practice. |
| 260 | +- **Encapsulate**: Keep fields private in POJOs—OOP rules! |
| 261 | +- **Test Errors**: Try bad IDs—ensure handling works. |
| 262 | + |
| 263 | +### 4.2 Common Mistakes to Avoid |
| 264 | + |
| 265 | +- **No Check**: Forgot `if` condition? Crash returns—add it! |
| 266 | +- **Wrong Status**: Used `500` instead of `404`? Fix to match intent! |
| 267 | +- **Missing Imports**: `ResponseEntity` not imported? Eclipse auto-import helps! |
| 268 | +- **No Handler**: Forgot `@ExceptionHandler`? Errors leak—add it! |
| 269 | + |
| 270 | +### 4.3 Hands-On Exercises |
| 271 | + |
| 272 | +Try these: |
| 273 | + |
| 274 | +1. **New Error**: |
| 275 | + - Add a check in `getStudents()`—throw if `students.isEmpty()`—test empty list error. |
| 276 | +2. **Break It**: |
| 277 | + - Remove `@ExceptionHandler`—test `/api/students/99`—fix it back! |
| 278 | +3. **Custom Message**: |
| 279 | + - Change message to "Oops! Student ID " + studentId + " not found!"—test in Postman. |
| 280 | +4. **Timestamp Play**: |
| 281 | + - Use `new Date().toString()` instead of `System.currentTimeMillis()`—compare outputs. |
| 282 | +5. **Bad ID Test**: |
| 283 | + - Test `/api/students/-1`—screenshot the 404 response. |
| 284 | + |
| 285 | +>[!TIP] |
| 286 | +>Tweak and test—own exception handling! |
| 287 | + |
| 288 | +--- |
| 289 | + |
| 290 | +## 5. Wrapping Up |
| 291 | + |
| 292 | +### 5.1 Resources for Further Learning |
| 293 | + |
| 294 | +Level up: |
| 295 | + |
| 296 | +- **Spring REST**: [spring.io/guides/gs/rest-service](https://spring.io/guides/gs/rest-service/) - REST guide. |
| 297 | +- **Jackson**: [github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - Official repo. |
| 298 | +- **Exceptions**: [docs.spring.io/spring-framework/docs/current/javadoc-api/](https://docs.spring.io/spring-framework/docs/current/javadoc-api/) - Spring docs. |
| 299 | +- **Postman**: [learning.postman.com](https://learning.postman.com/) - Tutorials. |
| 300 | + |
| 301 | + |
| 302 | +### 5.2 Summary of Key Takeaways |
| 303 | + |
| 304 | +- **Problem**: Bad IDs crashed `jacksondemo`—ugly logs. |
| 305 | +- **Solution**: Four steps—custom error POJO, exception class, throw logic, handler. |
| 306 | +- **Demo**: Built `rest-api-exception-demo`—404 JSON responses for bad IDs. |
| 307 | +- **Tools**: Jackson (JSON), `ResponseEntity` (HTTP), `@ExceptionHandler` (catch). |
| 308 | + |
| 309 | +>[!TIP] |
| 310 | +>You’re an error-handling hero—REST stays smooth now! |
| 311 | + |
| 312 | +### 5.3 What’s Next |
| 313 | + |
| 314 | +- **5.6 - REST API - Global Exception Handling**: Handle errors app-wide. |
| 315 | +- **Later**: CRUD, databases—REST grows stronger! |
| 316 | + |
| 317 | +--- |
0 commit comments