Content ITV PRO
This is Itvedant Content department
Business Scenario
In this lab, we will enhance the REST API by adding request validation, proper HTTP status codes, ResponseEntity, custom exceptions, and global exception handling.
Pre-Lab Preparation
Phase 3 — Validation + ResponseEntity + Exception Handling
Module:
1) Advance Spring data JPA and production ready Rest APi
Add Validation Dependency
1
Open: pom.xml Inside <dependencies>, add:
<!-- Used for validating request data -->
<dependency>
Create Exception package
2
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
com.medicare.exception
DoctorNotFoundException.java file empty. We will add its implementation in the next step.Add Validation to Doctor
3
Open Doctor.java and Update it to:
package com.medicare.entity;
import java.math.BigDecimal;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
@Entity
@Table(name = "doctors")
public class Doctor { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Doctor name is required")
@Size(min = 3, max = 100, message = "Doctor name must be between 3 and
100 characters")
@Column(nullable = false)
private String name;
@NotBlank(message = "Specialization is required")
@Size(max = 100, message = "Specialization cannot exceed 100 characters")
@Column(nullable = false)
private String specialization;
@NotNull(message = "Experience is required")
@Min(value = 0, message = "Experience cannot be negative")
@Max(value = 60, message = "Experience cannot exceed 60 years")
@Column(name = "experience_years", nullable = false)
private Integer experienceYears;
@NotNull(message = "Consultation fee is required")
@Positive(message = "Consultation fee must be greater than zero")
@Column(name = "consultation_fee", nullable = false, precision = 10, scale = 2)
private BigDecimal consultationFee;
@Size(max = 500, message = "Photo URL cannot exceed 500 characters")
@Column(name = "photo_url")
private String photoUrl; public Doctor() {
}
public Doctor(
String name,
String specialization,
Integer experienceYears,
BigDecimal consultationFee,
String photoUrl) {
this.name = name;
this.specialization = specialization;
this.experienceYears = experienceYears;
this.consultationFee = consultationFee;
this.photoUrl = photoUrl;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
public String getSpecialization() {
return specialization;
}
public void setSpecialization(String specialization) {
this.specialization = specialization;
}
public Integer getExperienceYears() {
return experienceYears;
}
public void setExperienceYears(Integer experienceYears) {
this.experienceYears = experienceYears;
}
public BigDecimal getConsultationFee() {
return consultationFee;
}
public void setConsultationFee(BigDecimal consultationFee) {
this.consultationFee = consultationFee;
} public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
}Implement DoctorNotFoundException
4
package com.medicare.exception;
public class DoctorNotFoundException extends RuntimeException {
public DoctorNotFoundException(String message) {
super(message);
}
}Update DoctorService
5
package com.medicare.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.medicare.entity.Doctor;
import com.medicare.exception.DoctorNotFoundException;
import com.medicare.repository.DoctorRepository;
@Service
public class DoctorService {
private final DoctorRepository doctorRepository;
public DoctorService(DoctorRepository doctorRepository) {
this.doctorRepository = doctorRepository;
} public Doctor createDoctor(Doctor doctor) {
return doctorRepository.save(doctor);
}
public List<Doctor> getAllDoctors() { return doctorRepository.findAll(); }
public Doctor getDoctorById(Long id) { return findDoctorById(id); }
public Doctor updateDoctor(Long id, Doctor doctorDetails) {
Doctor existingDoctor = findDoctorById(id);
existingDoctor.setName(doctorDetails.getName());
existingDoctor.setSpecialization(doctorDetails.getSpecialization());
existingDoctor.setExperienceYears(doctorDetails.getExperienceYears());
existingDoctor.setConsultationFee(doctorDetails.getConsultationFee());
existingDoctor.setPhotoUrl(doctorDetails.getPhotoUrl());
return doctorRepository.save(existingDoctor);
}
public void deleteDoctor(Long id) {
Doctor doctor = findDoctorById(id);
doctorRepository.delete(doctor);
}
private Doctor findDoctorById(Long id) {
return doctorRepository.findById(id)
.orElseThrow(() ->
new DoctorNotFoundException("Doctor not found with id: " + id));
}
}Create ErrorResponse
6
com.medicare.response
package com.medicare.response;
import java.time.LocalDateTime;
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String message;
private String path; public ErrorResponse(
LocalDateTime timestamp,
int status,
String message,
String path) {
this.timestamp = timestamp;
this.status = status;
this.message = message;
this.path = path;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public String getPath() {
return path;
}
}{
"timestamp": "2026-09-17T14:30:00",
"status": 404,
"message": "Doctor not found with id: 50",
"path": "/api/doctors/50"
}Create Validation Error Response
7
Create ValidationErrorResponse.java inside the response package
package com.medicare.response;
import java.time.LocalDateTime;
import java.util.Map;public class ValidationErrorResponse {
private LocalDateTime timestamp;
private int status;
private String message;
private String path;
private Map<String, String> errors;
public ValidationErrorResponse(
LocalDateTime timestamp,
int status,
String message,
String path,
Map<String, String> errors) {
this.timestamp = timestamp;
this.status = status;
this.message = message;
this.path = path;
this.errors = errors;
}
public LocalDateTime getTimestamp() { return timestamp; }
public int getStatus() { return status; }
public String getMessage() { return message; }
public String getPath() { return path; }
public Map<String, String> getErrors() { return errors; }
}Create Global Exception Handler
8
Inside the exception package : Create GlobalExceptionHandler.java
package com.medicare.exception;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.medicare.response.ErrorResponse;
import com.medicare.response.ValidationErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class GlobalExceptionHandler { @ExceptionHandler(DoctorNotFoundException.class)
public ResponseEntity<ErrorResponse> handleDoctorNotFound(
DoctorNotFoundException exception,
HttpServletRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
exception.getMessage(),
request.getRequestURI()
);
return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(errorResponse);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationErrors(
MethodArgumentNotValidException exception,
HttpServletRequest request) {
Map<String, String> errors = new LinkedHashMap<>();
exception.getBindingResult()
.getFieldErrors()
.forEach(error -> errors.put(
error.getField(),
error.getDefaultMessage()
)); ValidationErrorResponse errorResponse = new ValidationErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
request.getRequestURI(),
errors
);
return ResponseEntity
.badRequest()
.body(errorResponse);
}
}Update DoctorController
9
package com.medicare.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.medicare.entity.Doctor;
import com.medicare.service.DoctorService;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/doctors")
public class DoctorController {
private final DoctorService doctorService;
public DoctorController(DoctorService doctorService) {
this.doctorService = doctorService;
}
@PostMapping
public ResponseEntity<Doctor> createDoctor(@Valid @RequestBody Doctor doctor) {
Doctor savedDoctor = doctorService.createDoctor(doctor);
return ResponseEntity.status(HttpStatus.CREATED).body(savedDoctor);
}
@GetMapping
public ResponseEntity<List<Doctor>> getAllDoctors() { List<Doctor> doctors = doctorService.getAllDoctors();
return ResponseEntity.ok(doctors);
}
@GetMapping("/{id}")
public ResponseEntity<Doctor> getDoctorById(@PathVariable Long id) {
Doctor doctor = doctorService.getDoctorById(id);
return ResponseEntity.ok(doctor);
}
@PutMapping("/{id}")
public ResponseEntity<Doctor> updateDoctor(@PathVariable Long id,
@Valid @RequestBody Doctor doctor) {
Doctor updatedDoctor = doctorService.updateDoctor(id, doctor);
return ResponseEntity.ok(updatedDoctor);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteDoctor(@PathVariable Long id) {
doctorService.deleteDoctor(id);
return ResponseEntity.noContent().build();
}
}Test Invalid Doctor
10
{
"name": "",
"specialization": "",
"experienceYears": -5,
"consultationFee": -100
}{
"timestamp": "2026-09-17T14:30:00",
"status": 400,
"message": "Validation failed",
"path": "/api/doctors",
"errors": {
"name": "Doctor name is required",
"specialization": "Specialization is required",
"experienceYears": "Experience cannot be negative",
"consultationFee": "Consultation fee must be greater than zero"
}
}Test Doctor That Doesn't Exist
11
{
"timestamp": "2026-09-17T14:35:00",
"status": 404,
"message": "Doctor not found with id: 9999",
"path": "/api/doctors/9999"
}Test Invalid UPDATE
12
{
"name": "",
"specialization": "", "experienceYears": -10, "consultationFee": 0 }
400 BAD REQUEST
Delete a Doctor That Doesn't Exist
13
{
"timestamp": "...",
"status": 404, "message": "Doctor not found with id: 1", "path": "/api/doctors/1" }
We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You have successfully enhanced the Medicare REST API with validation, ResponseEntity, proper HTTP status codes, and global exception handling.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) Advance Spring data JPA and production ready Rest APi
By Content ITV