12 Spring Core/Spring Boot Interview Questions
1. What is Spring Framework?
Spring Framework is an open-source application framework. We can also say that it is a lightweight inversion of control(IoC) container and aspect-oriented container framework for the Java platform. Spring handles the infrastructure so that we can focus on our application development. It was created by Rod Johnson. In 2003 Spring came into existence
product controller (UI layer) -> product service (Logic layer) -> product DAO (database code layer) -> DB, What happens if it's tightly coupled
if the ProductController
, ProductService
, ProductDAO
, and the database are tightly coupled, it means that each component is directly dependent on the others. This can lead to several issues:
Difficulty in Unit Testing: Each component cannot be tested independently as it relies on the other components. This makes unit testing challenging.
Inflexibility: Changes in one component might require changes in all other components. For example, if you want to switch to a different database or change the way data is accessed, you might need to modify the
ProductDAO
,ProductService
, and potentially even theProductController
.Hard to Maintain and Scale: As the application grows, tightly coupled architecture can become increasingly complex and hard to manage. It’s also more difficult to scale specific parts of the application independently.
Less Reusability: Since the components are tightly bound to each other, reusing a component independently becomes difficult.
Dependency Injection (DI) is a technique to overcome these problems. With DI, we can inject dependencies (like ProductService
into ProductController
, or ProductDAO
into ProductService
) at runtime, making our components loosely coupled. This improves the flexibility and testability of our application. Here’s how it might look:
@Controller
public class ProductController {
private ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
//...
}
@Service
public class ProductService {
private ProductDAO productDAO;
@Autowired
public ProductService(ProductDAO productDAO) {
this.productDAO = productDAO;
}
//...
}
@Repository
public class ProductDAO {
// DB related operations
}
In this example, ProductController
doesn’t need to know about ProductDAO
and the database. It only interacts with ProductService
. Similarly, ProductService
doesn’t need to know about ProductController
, it only interacts with ProductDAO
.
2. Why spring is lightweight?
Spring is considered lightweight compared to traditional Java EE applications. If we want to run a Java EE application, we can't just create a small application that will run on its own. We shall need a Java EE application server to run our application such as Glassfish, Wildfly, WebLogic, Websphere etc. Most application servers are big and complex pieces of software, that are not trivial to install or configure. Hence If we use Spring then we won't need such things.
Secondly, Spring provides various modules for different purposes. These modules are grouped into Core Container, Data Access/Integration, Web, AOP (Aspect Oriented Programming), Instrumentation, Messaging, and Test, as shown in the following diagram. To use one or part of the module we don't need to inject all the modules. For example, we can use Spring JDBC without Spring Web.
3. What is Inversion of Control (IoC)?
Inversion of Control is a principle in Software Engineering by which the control of objects or portions of a program is transferred to a container or framework.
For example, say our application has a text editor component and we want to provide spell-checking. Our standard code would look something like this:
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
Here TextEditor needs a SpellChecker object. This Means TextEditor is dependent on SpellChecker and we are manually instantiating the TextEditor object. This means we are managing the dependency. This means we have the control. Now look at the below code:
public class TextEditor {
private SpellChecker checker;
public TextEditor(SpellChecker checker) {
this.checker = checker;
}
}
Here we are asking the Spring to instantiate the SpellChecker object and pass in the constructor of TextEditor i.e. Constructor Injection. This means Spring is managing the dependency. Now the control is transferred from the Programmer to Spring. This is nothing but an Inversion of Control.
Advantages of IOC:
- Loose Coupling:
- Without IoC:
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
- With IOC
public class TextEditor {
private SpellChecker checker;
public TextEditor(SpellChecker checker) {
this.checker = checker;
}
}
The second example demonstrates IoC through constructor injection. The TextEditor class is no longer responsible for creating its own dependencies, promoting loose coupling between TextEditor and SpellChecker.
- Dependency Injection (DI):
- Without IoC
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
- With IoC (Constructor Injection)
public class TextEditor {
private SpellChecker checker;
public TextEditor(SpellChecker checker) { this.checker = checker;
}
}
Dependency Injection makes components more testable and allows for easier substitution of dependencies, facilitating unit testing.
- Flexible Configuration:
- Without IoC:
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
- With IoC (XML Configuration):
<beans>
<bean id="textEditor" class="com.example.TextEditor"> <constructor-arg ref="spellChecker"/>
</bean>
<bean id="spellChecker" class="com.example.SpellChecker"/>
</beans>
IoC containers like Spring allow centralized configuration, making it easy to modify and adapt the application without changing the code.
- Enhanced Testability:
- *Without IoC:
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
- With IoC (Mocking for Testing):
public class TextEditorTest {
@Test
public void testTextEditor() {
SpellChecker mockChecker = mock(SpellChecker.class); TextEditor textEditor = new TextEditor(mockChecker); // Perform test using mockChecker
}
}
IoC makes it easier to inject mock objects during testing, allowing for more effective unit testing.
- Centralized Control
- Without IoC:
public class TextEditor {
private SpellChecker checker = new SpellChecker();
}
- With IoC (Spring Container):
// In the main application class or configuration file
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
TextEditor textEditor = context.getBean("textEditor", TextEditor.class);
IoC containers provide centralized control over the instantiation and management of objects, improving maintainability.
4. What is an aspect-oriented container framework?
Aspect-Oriented Programming (AOP) is a programming paradigm that addresses the modularization of cross-cutting concerns in software development. Cross-cutting concerns are aspects of a program that affect multiple modules and are often tangled or scattered throughout the codebase. AOP provides a way to modularize these concerns, making the code more modular, maintainable, and easier to understand.
Let's break down the key concepts in AOP:
Core Concerns:
Definition: Core concerns are the essential features or functionalities that are critical to the application's purpose. They represent the primary business logic and functionality.
Example: In a medical records application, handling and indexing medical records would be a core concern. These are the functionalities without which the application would lose its primary purpose.
Cross-Cutting Concerns:
Definition: Cross-cutting concerns are aspects of a program that affect multiple modules or components. They are functionalities that are commonly needed across different parts of the application.
Example: Logging, security, transaction management, and caching are typical cross-cutting concerns. These aspects do not belong to the core business logic but are necessary for various parts of the application.
AOP's Role:
Separation of Concerns: AOP aims to separate cross-cutting concerns from core concerns, allowing developers to modularize and manage them independently. This separation leads to cleaner, more maintainable code.
Modularization: AOP achieves modularization by introducing constructs called "aspects." Aspects encapsulate cross-cutting concerns, and they can be applied to the codebase without modifying the core concerns.
Aspect: An aspect is a module that encapsulates a cross-cutting concern. It defines what should happen and where it should occur in the code.
Advantages of AOP:
Code Reusability: Aspects can be reused across different modules, promoting code reusability.
Improved Maintainability: Separating cross-cutting concerns makes it easier to maintain and update specific functionalities without affecting the entire codebase.
Enhanced Readability: AOP allows developers to focus on core concerns in one place, improving the readability and understandability of the code.
Reduced Code Duplication: AOP helps eliminate code duplication by centralizing the implementation of cross-cutting concerns.
Example Scenario:
- Without AOP:
public void doSomething() {
// Core concern
// Business logic
// Cross-cutting concern
Logger.log("Something happened");
}
- With AOP:
public void doSomething() {
// Core concern
// Business logic
}
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.Service.*(..))")
public void logBefore() {
Logger.log("Something happened");
}
}
In this example, the logging concern is separated into an aspect (LoggingAspect), and it can be applied to various parts of the application without modifying their core logic.
In summary, AOP is a methodology that enhances modularity by isolating and encapsulating cross-cutting concerns, leading to more maintainable and readable code. Aspects in AOP provide a way to manage and apply these concerns in a centralized manner.
5. What is POJO class?
POJO stands for "Plain Old Java Object." It is a term used in Java development to describe a simple Java object that doesn't follow any complex framework or inheritance hierarchy, and it adheres to some basic conventions. The term "Plain Old" emphasizes that a POJO is a simple and straightforward Java class without any special restrictions or requirements imposed by a framework.
Key characteristics of a POJO class include:
No Framework Dependencies: A POJO should not be tied to any specific framework or technology. It does not extend or implement any framework-specific classes or interfaces.
Serializable: A POJO class often implements the
Serializable
interface to make instances of the class serializable, allowing them to be easily converted into byte streams for purposes like data storage or network transmission.Public Default Constructor: A POJO typically has a public default (no-argument) constructor. This is important for frameworks that instantiate objects via reflection.
Getters and Setters: A POJO class often includes standard getter and setter methods for accessing and modifying its properties. This helps with encapsulation and follows JavaBeans conventions.
Encapsulation: POJOs follow the principles of encapsulation, where the internal state of the object is kept private, and access to it is controlled through getter and setter methods.
Here's an example of a simple POJO class:
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// Public default constructor
public Person() {
}
// Parameterized constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter and setter methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
POJO stands for "Plain Old Java Object." It is a term used in Java development to describe a simple Java object that doesn't follow any complex framework or inheritance hierarchy, and it adheres to some basic conventions. The term "Plain Old" emphasizes that a POJO is a simple and straightforward Java class without any special restrictions or requirements imposed by a framework.
Key characteristics of a POJO class include:
No Framework Dependencies: A POJO should not be tied to any specific framework or technology. It does not extend or implement any framework-specific classes or interfaces.
Serializable: A POJO class often implements the
Serializable
interface to make instances of the class serializable, allowing them to be easily converted into byte streams for purposes like data storage or network transmission.Public Default Constructor: A POJO typically has a public default (no-argument) constructor. This is important for frameworks that instantiate objects via reflection.
Getters and Setters: A POJO class often includes standard getter and setter methods for accessing and modifying its properties. This helps with encapsulation and follows JavaBeans conventions.
Encapsulation: POJOs follow the principles of encapsulation, where the internal state of the object is kept private, and access to it is controlled through getter and setter methods.
Here's an example of a simple POJO class:
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// Public default constructor
public Person() {
}
// Parameterized constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter and setter methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
In this example, the Person
class is a POJO. It has a default constructor, implements Serializable
, includes private fields with corresponding getters and setters, and does not have dependencies on any specific framework.
6. How to disable AutoConfiguration ?
If you find that specific auto-configure classes are being applied that you don’t want, you can use the exclude attribute of @EnableAutoConfiguration
to disable them.
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.*;
import org.springframework.context.annotation.*;
@Configuration@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})publicclass MyConfiguration {
}
If the class is not on the classpath, you can use the excludeName
attribute of the annotation and specify the fully qualified name instead.
7. How to remove Embedded Tomcat Server in Spring Boot ?
By Default- Tomcat:-
When we add spring-boot-starter-web dependency as part of our web application(pom.xml) with spring boot. It includes tomcat along with all the dependencies. It's very convenient to use as we don't need to do anything and it's auto deployable to tomcat.
<dependency>
<groupId>org.springframework.boot></groupId>
<artifactId>spring-boot-starter-web></artifactId>
</dependency>
Exclude Tomcat:-
If we want to exclude tomcat from spring boot, we don't need to do much, we just need to add one additional block(<exclusions>) to the Spring Boot dependency.
<exclusions> tag is used to make us sure that given server/artifactId is being removed at the time of build.
Let's see how we can remove it:-
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artufactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
Above approach can be used to exclude Tomcat from Spring Boot and also for other exclusions as well.
8. What is the difference between an embedded container and a WAR?
The main difference between a WAR file & an embedded system is that an embedded container allows Spring Boot applications to run as a JAR directly from the command prompt without setting up any web server. However, to run a WAR file, you need to set up a web server first like Tomcat, which has a Servlet container, then you need to deploy WAR to run Spring Boot applications.
9. What are profiles?
Profiles in Spring Boot are a way to define different sets of configurations for your application depending on the environment it is being run in.
For example, you might have one set of configurations for your development environment and another set of configurations for your production environment. These configurations might include things like database settings (I want to use a database for tests and another for dev purposes ), Bean Creation (ex: I want a bean to be created only if I’m in the development process it’s possible with profiles ), ….
Profiles can be defined using property files, YAML files, or even Java code. By default, Spring Boot will use the “default” profile if no other profile is specified. To activate a profile, you can set the “spring.profiles.active” property to the name of the profile you want to use.
Profile Activation
Profiles can be defined using property files or YAML files. For instance, you might have an application-dev.properties
file for your development environment and an application-prod.properties
file for your production environment. To activate a profile, set the spring.profiles.active
property in your application.properties
file:
propertiesCopy code
spring.profiles.active=dev
This will activate the dev
profile, and Spring Boot will load the application-dev.properties
file.
Note: When you define the spring.profiles.active
property in your application.properties
file, Spring Boot will still load both the application.properties
file and the properties file specific to the active profile. This means that any properties defined in the application.properties
file will be overridden by properties defined in the active profile’s properties file if they have the same key.
10. What is the difference between Authentication and Authorization
Authentication and authorization are two fundamental concepts in computer security, often used together but serving distinct purposes. Here's a detailed explanation of the differences between them:
Authentication
Definition:
Authentication is the process of verifying the identity of a user or entity. It answers the question, "Who are you?"
Purpose:
The primary purpose of authentication is to confirm that the user or entity attempting to access a system is who they claim to be.
How It Works:
Authentication typically involves the use of credentials, such as:
Something you know: Passwords, PINs, or answers to security questions.
Something you have: Security tokens, smart cards, or mobile devices.
Something you are: Biometric data, such as fingerprints, facial recognition, or iris scans.
Examples:
Logging into a website with a username and password.
Using a fingerprint to unlock a smartphone.
Scanning a badge to enter a secure building.
Outcome:
Successful authentication provides proof of identity and allows the user or entity to proceed to the next step, often authorization.
Authorization
Definition:
Authorization is the process of determining what an authenticated user or entity is allowed to do. It answers the question, "What can you do?"
Purpose:
The primary purpose of authorization is to enforce access control by defining and restricting what resources or actions an authenticated user is permitted to access or perform.
How It Works:
Authorization typically involves permissions, roles, or policies that define access levels. This can include:
Role-based access control (RBAC): Users are assigned roles, and each role has specific permissions.
Attribute-based access control (ABAC): Access decisions are based on attributes (e.g., user attributes, resource attributes, environmental conditions).
Policy-based access control: Access is governed by policies that define rules and conditions for access.
Examples:
A user can view but not edit a document.
An administrator can create, delete, and modify users in a system, while a regular user can only view their own profile.
A system that restricts access to certain files based on user roles or group membership.
Outcome:
Authorization determines the level of access or actions that an authenticated user is allowed to perform within a system. It is only relevant after authentication has been successfully completed.
Key Differences
Function:
Authentication is about verifying identity.
Authorization is about granting permissions and access.
Sequence:
- Authentication always comes before authorization. A system must first authenticate a user before it can authorize their access to resources.
Information Used:
Authentication uses credentials (e.g., passwords, biometrics) to verify identity.
Authorization uses permissions, roles, or policies to control access to resources.
Focus:
Authentication focuses on who the user is.
Authorization focuses on what the user is allowed to do.
11. Optimizing Queries with @Query Annotation in Spring Data JPA
1. Use JPQL or Native SQL Appropriately
JPQL: Use JPQL for queries that are easily expressible in terms of entities and their relationships. JPQL is portable and easier to read and maintain.
Native SQL: Use native SQL when you need database-specific features, complex joins, or when performance requirements dictate bypassing the JPA provider.
2. Select Only Required Fields
Projection: Instead of fetching entire entities, select only the necessary fields. You can use JPQL projections or native SQL result mappings.
@Query("SELECT new com.example.dto.UserDTO(u.id, u.name) FROM User u WHERE u.active = true") List<UserDTO> findActiveUsers();
3. Pagination and Limiting Results
Pagination: Use pagination to limit the number of results returned. This can be done by adding a
Pageable
parameter to the method signature.javaCopy code @Query("SELECT u FROM User u WHERE u.active = true") Page<User> findActiveUsers(Pageable pageable);
Limiting Results: Use
LIMIT
in native queries orTOP
/FETCH FIRST
as supported by the database.@Query(value = "SELECT * FROM users WHERE active = true LIMIT 10", nativeQuery = true) List<User> findTop10ActiveUsers();
4. Optimizing Conditions and Joins
Filter Early: Apply filters in the
WHERE
clause to reduce the result set as early as possible.Efficient Joins: Use joins efficiently. Avoid unnecessary joins and ensure proper indexing of join columns.
@Query("SELECT o FROM Order o JOIN o.customer c WHERE c.status = 'ACTIVE'") List<Order> findOrdersForActiveCustomers();
5. Index Awareness
- Query with Indexes: Ensure that the fields used in
WHERE
clauses, joins, and ordering are indexed. This helps the database quickly locate the relevant data.
6. Avoid N+1 Query Problem
Fetch Strategies: Use appropriate fetch strategies to avoid the N+1 query problem, where lazy loading results in many small queries. Consider using
JOIN FETCH
to eagerly load associations when necessary.@Query("SELECT p FROM Post p JOIN FETCH p.comments WHERE p.id = :postId") Post findPostWithComments(@Param("postId") Long postId);
7. Named Queries
Use Named Queries: Named queries are precompiled at application startup, which can lead to faster execution times compared to dynamically defined queries.
@NamedQuery(name = "User.findByStatus", query = "SELECT u FROM User u WHERE u.status = :status")
8. Parameter Binding
Use Parameter Binding: Always use parameter binding instead of concatenating query strings to prevent SQL injection and improve query parsing efficiency.
@Query("SELECT u FROM User u WHERE u.name = :name") List<User> findByName(@Param("name") String name);
9. Caching
Second-Level Cache: If using Hibernate, consider enabling the second-level cache for entities that are frequently read but rarely modified.
Query Caching: Enable query caching for frequently run queries if the data does not change often.
10. Monitoring and Profiling
Monitor Query Performance: Use tools like Spring Boot Actuator, database logs, or APM tools to monitor the performance of your queries.
Profile Queries: Use database profiling tools to analyze the query execution plans and optimize them as needed.
Example of an Optimized Query with @Query Annotation
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.status = :status ORDER BY u.lastLogin DESC")
List<User> findActiveUsers(@Param("status") String status, Pageable pageable);
@Query("SELECT new com.example.dto.UserDTO(u.id, u.name) FROM User u WHERE u.status = :status")
List<UserDTO> findUserDTOsByStatus(@Param("status") String status);
@Query(value = "SELECT * FROM users WHERE status = ?1 LIMIT 10", nativeQuery = true)
List<User> findTop10UsersByStatus(String status);
}
12. Validator In SpringBoot
In Spring Boot, validators are used to enforce constraints on the data, ensuring that it meets specific criteria before being processed or persisted. Spring Boot provides robust support for validation through the use of the Bean Validation API (also known as JSR 380), with Hibernate Validator as the reference implementation.
1. Basic Concepts
Constraint Annotations: These are used to specify the validation rules on the fields of a class. Examples include
@NotNull
,@Size
,@Min
,@Max
, and@Email
.Validator Interface: Custom validations can be implemented by creating classes that implement the
ConstraintValidator
interface.@Valid
and@Validated
Annotations: These annotations are used to trigger validation.@Valid
is used at the parameter level in method signatures, while@Validated
can be used at the class level.
2. Using Built-in Validators
Example: Validating a Request Body
Suppose you have a User
class that you want to validate before processing it in a REST API.
javaCopy code
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
@NotNull(message = "Name cannot be null")
@Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters")
private String name;
@NotNull(message = "Email cannot be null")
@Email(message = "Email should be valid")
private String email;
// Getters and Setters
}
Example: Using @Valid
in a Controller
In your Spring Boot controller, you can use the @Valid
annotation to trigger validation.
javaCopy code
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody User user) {
// If validation fails, a MethodArgumentNotValidException will be thrown
return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}
}
If the User
object does not meet the validation criteria, a MethodArgumentNotValidException
will be thrown, and an appropriate error response can be returned.
3. Custom Validators
Sometimes, built-in constraints are not enough, and you may need to define custom validation logic.
Example: Custom Constraint Annotation
Define a custom annotation:
javaCopy code
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = AgeValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidAge {
String message() default "Invalid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Example: Custom Validator Implementation
Implement the validation logic:
javaCopy code
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class AgeValidator implements ConstraintValidator<ValidAge, Integer> {
@Override
public void initialize(ValidAge constraintAnnotation) {
}
@Override
public boolean isValid(Integer age, ConstraintValidatorContext context) {
return age != null && age >= 18 && age <= 100; // Custom validation logic
}
}
Example: Using Custom Validator
Use the custom annotation in your data model:
javaCopy code
public class User {
@NotNull(message = "Name cannot be null")
private String name;
@ValidAge
private Integer age;
// Getters and Setters
}
4. Global Exception Handling
To handle validation errors globally, you can use an exception handler.
Example: Global Exception Handler
javaCopy code
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 java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@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 new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
5. Group Validation
Spring Boot allows grouping validations using the @Validated
annotation. This is useful when you want to apply different validation rules in different contexts.
javaCopy code
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
public interface UserValidationGroups {
interface BasicInfo {}
interface DetailedInfo extends BasicInfo {}
@GroupSequence({Default.class, BasicInfo.class, DetailedInfo.class})
interface Complete {}
}
javaCopy code
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
@NotNull(message = "Name cannot be null", groups = UserValidationGroups.BasicInfo.class)
@Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters", groups = UserValidationGroups.BasicInfo.class)
private String name;
@ValidAge(groups = UserValidationGroups.DetailedInfo.class)
private Integer age;
// Getters and Setters
}
In your controller, you can specify which validation group to apply:
javaCopy code
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("/basic")
public ResponseEntity<String> createUserBasic(@Validated(UserValidationGroups.BasicInfo.class) @RequestBody User user) {
return ResponseEntity.status(HttpStatus.CREATED).body("User basic info validated");
}
@PostMapping("/detailed")
public ResponseEntity<String> createUserDetailed(@Validated(UserValidationGroups.DetailedInfo.class) @RequestBody User user) {
return ResponseEntity.status(HttpStatus.CREATED).body("User detailed info validated");
}
}
Conclusion
Spring Boot provides comprehensive support for validation, making it easy to apply both built-in and custom validation rules to your data models. By leveraging the Bean Validation API, you can ensure data integrity and enforce business rules effectively. Additionally, with global exception handling, you can provide meaningful error messages to the client, improving the overall user experience.
Subscribe to my newsletter
Read articles from Murali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by