Skip to content

Commit b4ea219

Browse files
committed
feat: add RESTful services for student management with exception handling
1 parent a8957dc commit b4ea219

File tree

7 files changed

+475
-0
lines changed

7 files changed

+475
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
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: BoilerplateLombok (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 IDThrows `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 POJOsOOP 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+
---
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.example.rest;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class JacksonProjectApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(JacksonProjectApplication.class, args);
11+
}
12+
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.example.rest.controller;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import com.example.rest.entity.Student;
15+
import com.example.rest.entity.StudentErrorReponse;
16+
17+
import exception.StudentNotFoundException;
18+
import jakarta.annotation.PostConstruct;
19+
20+
@RestController
21+
@RequestMapping("/api")
22+
public class StudentRestController {
23+
List<Student> students = new ArrayList<Student>();
24+
25+
@PostConstruct
26+
public void loadStudents() {
27+
28+
students.add(new Student("Leong", "Rizel"));
29+
students.add(new Student("Mario", "Rossie"));
30+
students.add(new Student("Razel", "Moore"));
31+
students.add(new Student("Samson", "Clich"));
32+
students.add(new Student("Amir", "Singh"));
33+
}
34+
35+
@GetMapping("/students")
36+
public List<Student> getStudents() {
37+
return students;
38+
}
39+
40+
@GetMapping("/students/{studentID}")
41+
public Student getStudent(@PathVariable int studentID) {
42+
43+
if (studentID >= students.size() || studentID < 0) {
44+
throw new StudentNotFoundException("student id not found - " + studentID);
45+
46+
}
47+
return students.get(studentID);
48+
}
49+
50+
@ExceptionHandler
51+
public ResponseEntity<StudentErrorReponse> handleException(StudentNotFoundException e) {
52+
53+
StudentErrorReponse error = new StudentErrorReponse();
54+
error.setStatus(HttpStatus.NOT_FOUND.value());
55+
error.setMesssage(e.getMessage());
56+
error.setTimeStamp(System.currentTimeMillis());
57+
58+
return new ResponseEntity<StudentErrorReponse>(error, HttpStatus.NOT_FOUND);
59+
60+
}
61+
}

0 commit comments

Comments
 (0)