Backend Qa
Backend Qa
answers. These questions are commonly asked and cover fundamental concepts.
Explanation:
Main Goals/Advantages:
* Stand-alone Applications: Embeds popular web servers (Tomcat, Jetty, Undertow) directly into
the executable JAR, so you can run them with java -jar. No need for WAR deployments or
external application servers.
* Opinionated Defaults: Provides sensible default configurations for common tasks, reducing
the need for explicit setup. You can, of course, override these defaults.
* No XML Configuration: Largely eliminates the need for XML configuration, favoring Java-
based configuration and annotations.
* Microservice Friendly: Its stand-alone nature and ease of deployment make it an excellent
choice for building microservices.
Explanation:
This is a common misconception. Spring Boot is not a replacement for Spring, but rather an
extension of the Spring Framework.
* Spring Framework:
* Provides core functionalities like IoC (Inversion of Control), DI (Dependency Injection), AOP
(Aspect-Oriented Programming), transaction management, data access, etc.
* Can be complex to set up initially due to extensive configuration (historically XML, now
mostly Java Config).
* Requires external web servers (like Tomcat, Jetty) for web applications.
* Spring Boot:
* Essentially, Spring Boot makes it much easier to get started and run Spring applications
quickly and efficiently. You still write Spring code; Spring Boot just handles much of the
boilerplate configuration for you.
Explanation:
How it Works:
* Classpath Scanning: When your application starts, Spring Boot scans the classpath for starter
dependencies and specific classes.
* Intelligent Configuration: Based on what it finds, it intelligently configures beans for you.
* User Overrides: Importantly, auto-configuration is designed to back off if you define your own
beans of the same type. For example, if you define your own DataSource bean, Spring Boot's
auto-configured one will not be used.
Advantages: Reduces development time and configuration burden, making it easier to build
applications quickly.
Explanation:
Spring Boot Starters are a set of convenient dependency descriptors that you can include in your
build configuration (Maven pom.xml or Gradle build.gradle). They are pre-packaged sets of
dependencies that bring in everything you typically need for a particular module or functionality.
Purpose:
* Consistency: Ensures you have a consistent and tested set of dependencies for a given
purpose.
* Opinionated Defaults: Starters are tightly coupled with Spring Boot's auto-configuration,
providing sensible defaults for the included libraries.
Example:
* spring-boot-starter-web:
* It brings in:
* Validation APIs
Explanation:
What it comprises:
* @SpringBootConfiguration:
* @EnableAutoConfiguration:
* @ComponentScan:
* Enables component scanning within the current package and its sub-packages.
* This annotation tells Spring to automatically detect and register Spring components (like
@Component, @Service, @Repository, @Controller, @RestController) as beans in the
application context.
Usage:
You typically put this annotation on your main class, which also contains the main method to
run the application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
SpringApplication.run(MySpringApplication.class, args);
Explanation:
These are core concepts of the Spring Framework, and Spring Boot heavily relies on them.
* It's a design principle where the control of object creation and lifecycle management is
inverted (transferred) from the application code to a framework (the IoC container).
* Traditionally, your code would create objects it needs. With IoC, the framework creates and
manages the objects, and provides them to your code when needed.
* Benefit: Reduces coupling between components, making the code more modular, testable,
and maintainable.
* Instead of components creating their dependencies, they declare their dependencies, and the
IoC container "injects" these dependencies into the component at runtime.
* How it works: Spring scans your code for dependencies (e.g., fields annotated with
@Autowired) and automatically provides the required objects (beans) from its container.
@Service
this.myRepository = myRepository;
@Service
this.myRepository = myRepository;
* Field Injection (Least Recommended, but common): Dependencies are injected directly into
fields using @Autowired.
@Service
@Autowired
* Disadvantages: Makes testing harder (cannot easily inject mocks without Spring context),
hides dependencies, encourages mutable objects.
Explanation:
An embedded server is a web server (like Tomcat, Jetty, or Undertow) that is directly packaged
within the executable JAR file of your Spring Boot application.
Why it is Useful:
* Stand-alone Applications: It makes your Spring Boot application a self-contained unit. You can
simply run it using java -jar your-app.jar without needing to install or configure an external
application server beforehand.
* Simplified Deployment: No need to build WAR files and deploy them to a separate application
server. This greatly simplifies the deployment process, especially in containerized environments
(Docker) and cloud platforms.
* Faster Development Cycles: Eliminates the overhead of server setup and deployment during
development, leading to quicker iterations.
* Consistent Environment: Ensures that the same server environment is used in development,
testing, and production, reducing "works on my machine" issues.
By default, Spring Boot uses Tomcat. You can easily switch to Jetty or Undertow by including
their respective starter dependencies and excluding Tomcat's.
Explanation:
server.port=8081
spring.datasource.url=jdbc:h2:mem:testdb
my.custom.property=Hello
server:
port: 8081
spring:
datasource:
url: jdbc:h2:mem:testdb
my:
custom:
property: Hello
* You can inject property values directly into your Spring components using the @Value
annotation.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Value("${my.custom.property}")
* Using @ConfigurationProperties:
app.settings.name=My App
app.settings.version=1.0
app.settings.security.enabled=true
// In Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app.settings")
}
* Command-line Arguments: You can also pass properties as command-line arguments when
running the JAR:
* Environment Variables: Properties can also be sourced from environment variables (e.g.,
SERVER_PORT=9000). Spring Boot maps them automatically.
Explanation:
Creating a RESTful API endpoint in Spring Boot is straightforward, primarily using Spring MVC
annotations.
Steps:
* @ResponseBody: Indicates that the return value of a method should be bound directly to the
web response body (often as JSON or XML), skipping view resolution.
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.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController // Combines @Controller and @ResponseBody
@GetMapping("/hello")
@GetMapping("/greet/{name}")
@PostMapping("/users")
}
// Assume User class exists:
// }
10. What are Spring Profiles, and why are they useful?
Explanation:
Spring Profiles provide a way to separate different parts of your application configuration and
make them available only in certain environments. You can activate different profiles based on
the environment where your application is running (e.g., dev, test, prod).
* Conditional Bean Creation: You can define beans that are created only when a specific profile
is active.
* Simplifies Deployment: Reduces the need for manual configuration changes during
deployment transitions.
How to Use:
* Example:
* application-dev.properties:
spring.datasource.url=jdbc:h2:mem:devdb
server.port=8080
logging.level.root=DEBUG
* application-prod.properties:
spring.datasource.url=jdbc:mysql://prod-db:3306/mydb
server.port=80
logging.level.root=INFO
@Configuration
@Profile("dev")
@Bean
@Configuration
@Profile("prod")
@Bean
}
}
* Activating Profiles:
spring.profiles.active=dev
* Command Line:
* Environment Variable:
SPRING_PROFILES_ACTIVE=test
Explanation:
Spring Boot Actuator is a sub-project of Spring Boot that provides production-ready features to
help you monitor and manage your application when it's running in production. It provides a set
of endpoints that expose operational information about the running application.
* Info: (/actuator/info) Displays arbitrary application info from properties (e.g., info.app.name,
info.app.version).
* Metrics: (/actuator/metrics) Exposes various metrics (e.g., JVM memory usage, HTTP
requests, garbage collection) collected by Micrometer. You can also define custom metrics.
* Loggers: (/actuator/loggers) Allows viewing and changing log levels of the application at
runtime.
* Beans: (/actuator/beans) Displays a complete list of all Spring beans in the application
context.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Security:
By default, most Actuator endpoints are exposed over HTTP. In production, it's crucial to secure
them using Spring Security or by configuring them to be accessible only internally. You can
configure which endpoints are exposed and via which technologies (web, JMX).
12. How does Spring Boot handle error handling in REST APIs?
Explanation:
Spring Boot provides several mechanisms for handling errors gracefully in REST APIs,
preventing raw stack traces from being sent to clients and allowing for standardized error
responses.
Common Approaches:
* You can define methods within a specific @Controller or @RestController class to handle
exceptions thrown by methods within that same controller.
@RestController
@RequestMapping("/api/items")
@GetMapping("/{id}")
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
* This is the recommended approach for centralized and global error handling across multiple
controllers.
@RestControllerAdvice
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ex.getBindingResult().getAllErrors().forEach((error) -> {
errors.put(fieldName, errorMessage);
});
return errors;
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
* For traditional web applications, Spring Boot can map error codes to specific error pages.
* For REST APIs, Spring Boot provides a default /error endpoint. You can customize this by
implementing ErrorController or by configuring specific error responses via
server.error.whitelabel.enabled=false and custom error handling beans.
* Standardized Error Responses: Always return consistent JSON (or XML) error objects,
including status codes, error messages, and potentially a unique error code.
* Appropriate HTTP Status Codes: Use correct HTTP status codes (e.g., 400 Bad Request, 401
Unauthorized, 403 Forbidden, 404 Not Found, 500 Internal Server Error).
* Logging: Log full stack traces on the server side for debugging, but never expose them
directly to the client.
This document covers some of the most frequently asked basic Spring Boot questions for
backend interviews. Remember to also be prepared to discuss related topics like Maven/Gradle,
testing (JUnit, Mockito), database interactions (JPA, JDBC), and basic security concepts.
Here's a comprehensive set of interview questions and answers focused on CRUD (Create, Read,
Update, Delete) operations in a Java backend using Spring Boot, covering the core concepts,
implementation, and best practices.
CRUD (Create, Read, Update, Delete) are the four basic operations of persistent storage. In the
context of a Java backend with Spring Boot, these operations typically involve interacting with a
database to manage data for various entities (e.g., User, Product, Order). Spring Boot, combined
with Spring Data JPA, simplifies the implementation of these operations significantly.
A typical Spring Boot application for CRUD will follow a layered architecture:
* Service Layer (Business Logic): Contains the core business rules, orchestrates operations,
and interacts with the repository layer.
* Repository Layer (Data Access): Interfaces with the database, performing the actual CRUD
operations. Spring Data JPA greatly simplifies this by providing ready-to-use methods.
* Model/Entity Layer: Represents the data structure (e.g., a Product entity mapped to a
products table).
* DTO (Data Transfer Object) Layer (Optional but Recommended): Used to transfer data
between layers (e.g., between Controller and Service) and to client applications, often for data
validation and to hide internal entity details.
1. Entity/Model (Product.java)
public Product() {}
this.description = description;
this.price = price;
@Override
return "Product{" +
"id=" + id +
'}';
}
2. Repository (ProductRepository.java)
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
// You can also define custom query methods just by declaring them:
3. Service (ProductService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
this.productRepository = productRepository;
// Create
if (productRepository.existsByName(product.getName())) {
return productRepository.save(product);
// Read (All)
return productRepository.findAll();
return productRepository.findById(id);
// Update
@Transactional
// Update fields
existingProduct.setName(productDetails.getName());
existingProduct.setDescription(productDetails.getDescription());
existingProduct.setPrice(productDetails.getPrice());
// Delete
@Transactional
if (!productRepository.existsById(id)) {
productRepository.deleteById(id);
}
4. Controller (ProductController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Autowired
this.productService = productService;
@PostMapping
@GetMapping
@GetMapping("/{id}")
return productService.getProductById(id)
@PutMapping("/{id}")
}
// DELETE operation: HTTP DELETE /api/products/{id}
@DeleteMapping("/{id}")
productService.deleteProduct(id);
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
super(message);
Explanation:
Spring Data JPA significantly simplifies CRUD operations by providing a powerful abstraction
layer over JPA (Java Persistence API).
* Eliminates Boilerplate Code: Instead of writing concrete implementations for common data
access methods (like save, findById, findAll, delete), you simply declare an interface that
extends one of Spring Data's repository interfaces (e.g., CrudRepository,
PagingAndSortingRepository, JpaRepository).
* Query Method Derivation: It can automatically create queries based on method names. For
example, findByProductName(String name) will generate the SQL query to find a product by its
name.
2. Describe the typical flow of a "Create" operation in a Spring Boot REST API.
Explanation:
The "Create" operation (often mapped to an HTTP POST request) typically involves the following
flow:
* Client Request (HTTP POST): The client sends an HTTP POST request to a specific endpoint
(e.g., /api/products) with the new resource's data in the request body (usually JSON).
POST /api/products
Content-Type: application/json
"name": "Laptop",
"description": "Powerful notebook",
"price": 1200.00
* @RequestBody deserializes the JSON request body into a Java object (e.g., Product entity or
a DTO).
* Basic validation (e.g., using @Valid and JSR 303/380 annotations) might occur here.
* The service layer applies business rules (e.g., check for duplicate names, perform data
transformations).
* save() inserts a new record into the database, and if the entity has an auto-generated ID, it
will be populated on the returned object.
* Database Interaction: Hibernate/JPA translates the save() call into an SQL INSERT statement
and executes it against the database.
* Service Response: The service returns the saved Product object (now with its generated ID) to
the controller.
* The controller returns a ResponseEntity with the newly created Product object in the
response body.
* It sets the HTTP status code to 201 Created to indicate successful resource creation. It
might also include a Location header pointing to the newly created resource's URI (e.g.,
/api/products/1).
3. How do you implement a "Read" operation (fetching all and fetching by ID) in Spring Boot?
Explanation:
"Read" operations are handled by HTTP GET requests.
* Controller: A method annotated with @GetMapping (no path specified or just /) handles this.
@GetMapping
return productRepository.findAll();
* Repository: JpaRepository's findAll() method fetches all entities from the corresponding table.
* Response: The controller returns a list of Product objects as JSON with an HTTP 200 OK
status.
@GetMapping("/{id}")
* Service Layer: Calls findById(id) on the repository, which returns an Optional<Product>. This
Optional is crucial for handling cases where the resource might not exist.
return productRepository.findById(id);
* Repository: JpaRepository's findById(ID id) method attempts to retrieve the entity by its
primary key.
* Response:
* If found: Returns the Product object as JSON with HTTP 200 OK.
4. How would you handle an "Update" operation (PUT request) in Spring Boot, considering partial
updates?
Explanation:
* PUT: Typically used for full replacement of a resource. The client sends the complete,
updated representation of the resource. If fields are missing from the request, they should be
set to null or default values in the database, effectively replacing the entire resource.
* PATCH: Used for partial updates. The client sends only the fields that need to be changed.
(This often requires more complex logic, possibly using libraries like Jackson's ObjectMapper to
merge changes or explicitly checking each field).
* Client Request (HTTP PUT): PUT /api/products/{id} with the complete updated Product data
in the body.
PUT /api/products/1
Content-Type: application/json
"price": 1250.00
@PutMapping("/{id}")
* Service Layer:
* Updates the fields of the existing entity with values from productDetails.
* Calls save() on the repository. If the entity has an ID, save() performs an UPDATE operation;
otherwise, it would perform an INSERT.
@Transactional
existingProduct.setName(productDetails.getName());
existingProduct.setDescription(productDetails.getDescription());
existingProduct.setPrice(productDetails.getPrice());
return productRepository.save(existingProduct);
* Database Interaction: Hibernate/JPA translates the save() call into an SQL UPDATE statement.
* Response: Returns the updated Product object with HTTP 200 OK.
For PATCH, you would typically use a similar service method, but instead of unconditionally
setting fields, you'd check if the incoming productDetails has a non-null value for a field before
updating it on existingProduct. For complex objects, using a DTO for input and mapping
frameworks like MapStruct or manual null-checks is common.
5. How do you perform a "Delete" operation in Spring Boot, and what status code should be
returned?
Explanation:
The "Delete" operation (mapped to an HTTP DELETE request) removes a resource from the
database.
DELETE /api/products/1
productService.deleteProduct(id);
* Service Layer:
* It's good practice to check if the resource exists before attempting to delete, to provide a
more specific error (e.g., 404 Not Found instead of a generic 500 Internal Server Error if
deleteById throws an exception for non-existent ID).
@Transactional
productRepository.deleteById(id);
* Database Interaction: Hibernate/JPA translates the deleteById() call into an SQL DELETE
statement.
* Response:
* If successful, the controller should return an HTTP 204 No Content status. This indicates
that the request was successful, but there is no content to return in the response body.
Explanation:
DTOs are simple Java classes that represent a subset or combination of data from one or more
domain entities, designed specifically for data transfer between different layers of an
application (e.g., between the web layer and the service layer, or between your API and the
client).
* Decoupling: DTOs decouple your API's external representation from your internal database
schema (JPA Entities). If your database schema changes, you might only need to update the
internal entity-to-DTO mapping, not necessarily the client-facing API contract.
* Validation: You can apply validation annotations (e.g., @NotNull, @Size, @Email) directly to
DTO fields, making it easier to validate incoming request data before it touches your business
logic or persistence layer. This is cleaner than validating directly on entities.
* Reduced Data Transfer: You can create DTOs that only contain the fields required for a
specific operation. For instance, a ProductCreateDTO might not include an id field, while a
ProductResponseDTO would. This reduces network payload size.
* Avoiding Circular References: In complex object graphs (e.g., User has Orders, Orders have
User), directly exposing entities can lead to infinite JSON serialization loops. DTOs help break
these cycles by selectively including references.
* Readability and Clarity: Clearly defines the expected input and output structure for your API
endpoints.
// ProductCreateRequest.java
@Size(min = 3, max = 100, message = "Name must be between 3 and 100 characters")
// ProductResponse.java
// In Controller:
@PostMapping
}
// In Service (converts DTO to Entity and vice versa, or use a dedicated mapper)
// ...
7. How do you implement input validation for CRUD operations in Spring Boot?
Explanation:
Input validation is crucial to ensure data integrity and prevent security vulnerabilities. Spring
Boot leverages the Jakarta Bean Validation API (JSR 303/380) along with Hibernate Validator as
its reference implementation.
Steps to Implement:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
Use the @Valid annotation on the @RequestBody parameter in your controller methods.
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@PostMapping("/api/products")
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.validation.FieldError;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ex.getBindingResult().getAllErrors().forEach((error) -> {
errors.put(fieldName, errorMessage);
});
return errors;
This setup ensures that clients receive a 400 Bad Request status with a clear JSON payload
detailing all validation errors.
Explanation:
Purpose in CRUD:
* Data Integrity: Prevents partial updates or inconsistent data states, especially in complex
operations involving multiple database modifications.
Where to Apply:
* Class Level: Can be applied at the class level (e.g., on ProductService) to make all public
methods within that class transactional by default.
Example:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
// Constructor injection
// Business logic...
return productRepository.save(product);
return productRepository.findById(id);
}
return productRepository.save(existingProduct);
productRepository.deleteById(id);
Note: findById() and findAll() from JpaRepository are usually safe to call outside explicit
@Transactional boundaries for simple reads, as Spring Data JPA often wraps them in internal
read-only transactions. However, explicitly adding @Transactional(readOnly = true) is a good
practice for clarity and potential optimization.
9. What are common HTTP status codes you would return for each CRUD operation and why?
Explanation:
Returning appropriate HTTP status codes is a fundamental aspect of designing a RESTful API.
They provide immediate feedback to the client about the outcome of their request without
needing to parse the response body.
* CREATE (POST):
* 201 Created: Indicates that the request has been fulfilled and resulted in a new resource
being created. The response usually includes a Location header pointing to the URI of the newly
created resource, and the resource itself in the response body.
* 400 Bad Request: If the request body is invalid (e.g., missing required fields, invalid data
format, validation errors).
* 409 Conflict: If the resource already exists and the operation would lead to a duplicate (e.g.,
trying to create a product with a name that must be unique).
* READ (GET):
* 200 OK: The request has succeeded. The response body contains the requested resource(s).
* 404 Not Found: The requested resource does not exist (e.g., trying to get a product by an ID
that doesn't exist).
* 400 Bad Request: If query parameters are invalid or missing for a valid request (e.g.,
pagination parameters are malformed).
* UPDATE (PUT/PATCH):
* 200 OK: The request has succeeded and the resource has been updated. The response body
can optionally contain the updated resource.
* 204 No Content: The request has succeeded, but there's no need to return a response body
(e.g., if the client already has the updated data or doesn't need it back). Often used with PUT.
* 400 Bad Request: If the request body is invalid (e.g., validation errors).
* DELETE (DELETE):
* 200 OK: The request has succeeded, and the response body contains a confirmation
message or the deleted resource (less common, 204 preferred).
* 204 No Content: The request has succeeded, and there is no content to send in the response
body. This is the most common and recommended status for successful DELETE operations.
10. How would you handle custom exceptions (e.g., ResourceNotFoundException) in CRUD
operations?
Explanation:
For better error handling and a more professional API, you should define custom exceptions for
specific business errors (like a resource not found) and then handle them centrally.
Steps:
* Define Custom Exception:
// ResourceNotFoundException.java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
super(message);
* When a business condition dictates an error (e.g., findById returns empty), throw your
custom exception from the service layer.
// In ProductService.java
return productRepository.save(existingProduct);
* Define methods annotated with @ExceptionHandler for each custom exception type (and
potentially a catch-all Exception.class).
// GlobalExceptionHandler.java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
@RestControllerAdvice
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object>
handleResourceNotFoundException(ResourceNotFoundException ex) {
body.put("status", HttpStatus.NOT_FOUND.value());
body.put("message", ex.getMessage());
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("message", ex.getMessage());
@ExceptionHandler(Exception.class)
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
body.put("error", "Internal Server Error");
This approach centralizes error handling, provides consistent error responses, and keeps your
controller methods clean from try-catch blocks for common exceptions.