Top 50 spring boot interviews : Security

Top 10 Questions on Spring Boot REST APIs with Answers

This entry is part 4 of 5 in the series Top 50 Interview Questions and Answers on Spring Boot

REST APIs

Question 31. How do you create a REST API in Spring Boot?

A REST API (Representational State Transfer Application Programming Interface) allows different systems to communicate over HTTP by exposing endpoints. These endpoints are accessible via standard HTTP methods like GET, POST, PUT, and DELETE. REST APIs are widely used because they are lightweight, scalable, and platform-independent.

Step 1: Setting Up Spring Boot Project
  1. Create a Spring Boot Project:
    • Go to Spring Initializr.
    • Select the following:
      • Project: Maven
      • Language: Java
      • Spring Boot Version: Choose the latest stable version.
      • Dependencies:
        • Spring Web (for building web applications)
        • Spring Boot DevTools (for hot-reloading during development)
        • H2 Database (optional, for in-memory testing)
    • Click Generate to download the project.
  2. Import the Project:
    • Open the project in your favorite IDE (IntelliJ IDEA, Eclipse, or VS Code).
    • Ensure that all dependencies are resolved by running mvn install or using the IDE’s build tools.
Step 2: Define Project Structure

Organizing your code is critical for maintainability. Your project structure will look something like this:

src/main/java/com/example/demo
    ├── controller
    ├── service
    ├── repository
    ├── model

Each package serves a distinct purpose:

  • Controller: Handles HTTP requests and responses.
  • Service: Contains business logic.
  • Repository: Interfaces with the database.
  • Model: Represents the data structure.
Step 3: Create a REST API Example

Let’s build a simple REST API for managing a list of “Books”.

Step 3.1: Create the Book Model

The model represents the structure of your data.

package com.example.demo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;

    public Book() {}

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
Step 3.2: Create the Repository

The repository is responsible for database interactions. Spring Data JPA simplifies this by providing.JpaRepository

package com.example.demo.repository;

import com.example.demo.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}
Step 3.3: Create the Service

The service contains the business logic for your application.

package com.example.demo.service;

import com.example.demo.model.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    public Book getBookById(Long id) {
        return bookRepository.findById(id).orElseThrow(() -> new RuntimeException("Book not found"));
    }

    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }

    public void deleteBook(Long id) {
        bookRepository.deleteById(id);
    }
}
Step 3.4: Create the Controller

The controller handles HTTP requests and maps them to the service layer.

package com.example.demo.controller;

import com.example.demo.model.Book;
import com.example.demo.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping
    public List<Book> getAllBooks() {
        return bookService.getAllBooks();
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookService.getBookById(id);
    }

    @PostMapping
    public Book createBook(@RequestBody Book book) {
        return bookService.saveBook(book);
    }

    @DeleteMapping("/{id}")
    public void deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
    }
}
Step 4: Test the REST API
  1. Run the Application:
    • Right-click on your main class (e.g., DemoApplication) and select Run.
  2. Access the Endpoints:
    • Use tools like Postman or cURL to test the endpoints:
      • GET /api/books: Fetch all books.
      • GET /api/books/{id}: Fetch a specific book by ID.
      • POST /api/books: Create a new book (pass JSON data in the body).
      • DELETE /api/books/{id}: Delete a book by ID.
  3. Example JSON for Creating a Book:
{
    "title": "Spring Boot in Action",
    "author": "Craig Walls"
}
Step 5: Advanced Concepts

1. Validation: Use @Valid and @NotNull annotations to validate input data. For example:

@NotNull(message = "Title is required")
private String title;

2. Pagination: Add pagination and sorting to your API using Spring Data JPA. For example:

@GetMapping
public Page<Book> getBooks(Pageable pageable) {
    return bookRepository.findAll(pageable);
}

3. Error Handling: Implement global exception handling using. @ControllerAdvice For instance:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

4. Security: Secure your API using Spring Security and OAuth2. This ensures that only authorized users can access specific endpoints.

5. Caching: Improve performance by adding caching mechanisms such as Redis or Ehcache.

Conclusion

By following this tutorial, you’ve learned how to create a REST API in Spring Boot. From setting up the project to handling HTTP requests, you’ve built a fully functional API for managing books.

Question 32. What is the difference between @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping?

Spring Boot provides several annotations to handle HTTP requests in RESTful web services. Among these, @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping are some of the most commonly used. Each of these annotations corresponds to a specific HTTP method and serves a unique purpose in handling requests.

Let’s break down each annotation in detail with examples

1. @GetMapping

The @GetMapping annotation is used to handle HTTP GET requests. These requests are typically used to retrieve data from the server without making any changes to the underlying data.

Example: Fetching a list of books

@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping
    public List<String> getAllBooks() {
        return List.of("Book 1", "Book 2", "Book 3");
    }

    @GetMapping("/{id}")
    public String getBookById(@PathVariable int id) {
        return "Book " + id;
    }
}
Explanation:
  • The first @GetMapping maps to /api/books and returns a list of books.
  • The second @GetMapping maps to /api/books/{id} and retrieves a specific book based on its ID.
2. @PostMapping

The @PostMapping annotation is used to handle HTTP POST requests. These requests are typically used to create new resources on the server.

Example: Adding a new book

@RestController
@RequestMapping("/api/books")
public class BookController {

    @PostMapping
    public String addBook(@RequestBody String bookName) {
        return "Book added: " + bookName;
    }
}
Explanation:
  • The @PostMapping maps to /api/books and allows the client to send a new book name in the request body.
  • The server processes the request and responds with a confirmation message.
3. @PutMapping

The @PutMapping annotation is used to handle HTTP PUT requests. These requests are typically used to update an existing resource.

Example: Updating a book’s details

@RestController
@RequestMapping("/api/books")
public class BookController {

    @PutMapping("/{id}")
    public String updateBook(@PathVariable int id, @RequestBody String updatedBookName) {
        return "Book " + id + " updated to: " + updatedBookName;
    }
}
Explanation:
  • The @PutMapping map to /api/books/{id} and allows the client to update the name of a specific book by providing its ID and new name in the request body.
4. @DeleteMapping

The @DeleteMapping annotation is used to handle HTTP DELETE requests. These requests are typically used to delete a resource from the server.

Example: Deleting a book

@RestController
@RequestMapping("/api/books")
public class BookController {

    @DeleteMapping("/{id}")
    public String deleteBook(@PathVariable int id) {
        return "Book " + id + " deleted.";
    }
}
Explanation:
  • The @DeleteMapping maps to /api/books/{id} and allows the client to delete a specific book by providing its ID.
  • The server processes the request and confirms the deletion.
Key Differences
AnnotationHTTP MethodPurposeExample Use Case
@GetMappingGETRetrieve dataFetching a list of books
@PostMappingPOSTCreate new resourcesAdding a new book
@PutMappingPUTUpdate existing resourcesModifying user books
@DeleteMappingDELETEDelete resourcesRemoving a book from the database
Key Differences @GetMapping, @putMapping, @postMapping, @deleteMapping
Conclusion

The @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping annotations in Spring Boot simplify the development of RESTful APIs by providing a clear and concise way to map HTTP methods to controller methods. Understanding their differences and appropriate use cases is crucial for building efficient and maintainable web services.

Question 33. How do you handle exceptions in a Spring Boot REST API?

Handling exceptions effectively is an important part of developing robust and user-friendly REST APIs. Spring Boot provides multiple ways to handle exceptions, ranging from basic exception handling to advanced global exception handling.

1. Why Handle Exceptions?

When building REST APIs, exceptions can occur due to various reasons, such as invalid inputs, database errors, or resource unavailability. Without proper handling, these exceptions can expose sensitive information or confuse users with unclear error messages. Exception handling ensures that your API returns meaningful and secure error responses.

2. Basic Exception Handling Using try-catch Blocks

For simple scenarios, you can use try-catch blocks to handle exceptions locally within a method.

Example: Handling an exception in a controller

@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping("/{id}")
    public ResponseEntity<String> getBookById(@PathVariable int id) {
        try {
            // Simulate fetching a book
            if (id <= 0) {
                throw new IllegalArgumentException("Invalid book ID");
            }
            return ResponseEntity.ok("Book " + id);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}
Explanation:
  • The try-catch block handles specific exceptions, like.IllegalArgumentException
  • Returns a 400 Bad Request response with a meaningful error message.
3. Using @ExceptionHandler for Specific Exceptions

Spring Boot allows you to define methods to handle specific exceptions using the @ExceptionHandler annotation.

Example: Handling specific exceptions

@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping("/{id}")
    public ResponseEntity<String> getBookById(@PathVariable int id) {
        try {
            // Simulate fetching a book
            if (id <= 0) {
                throw new IllegalArgumentException("Invalid book ID");
            }
            return ResponseEntity.ok("Book " + id);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}
Explanation:
  • The @ExceptionHandler method handles IllegalArgumentException globally for the controller.
  • Returns a 400 Bad Request response with the exception message.
5. Global Exception Handling with @ControllerAdvice

For larger applications, you can centralize exception handling using the @ControllerAdvice annotation. This approach applies exception handling logic across all controllers.

Example: Global exception handler

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unexpected error occurred: " + e.getMessage());
    }
}
Explanation:
  • @ControllerAdvice applies exception handling to all controllers.
  • Handles both specific (IllegalArgumentException) and generic (Exception) exceptions.
  • Ensures consistent error responses across the application
5. Custom Exception Handling

For better clarity and maintainability, you can define custom exception classes.

Example: Custom exception and handler

// Custom exception
public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException(String message) {
        super(message);
    }
}

// Controller
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping("/{id}")
    public String getBookById(@PathVariable int id) {
        if (id != 1) {
            throw new BookNotFoundException("Book not found with ID: " + id);
        }
        return "Book " + id;
    }
}

// Global handler
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
    }
}
Explanation:
  • This BookNotFoundException is a custom exception extending.RuntimeException
  • The global exception handler ensures a 404 Not Found response with a clear message.
6. Returning Standardized Error Responses

For a professional API, it’s important to return standardized error responses. You can create an error response model to achieve this.

Example: Standardized error response

// Error response model
public class ErrorResponse {
    private String message;
    private int status;
    private LocalDateTime timestamp;

    public ErrorResponse(String message, int status) {
        this.message = message;
        this.status = status;
        this.timestamp = LocalDateTime.now();
    }

    // Getters and setters
}

// Global handler
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleBookNotFoundException(BookNotFoundException e) {
        ErrorResponse errorResponse = new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND.value());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }
}
Explanation:
  • The ErrorResponse model includes fields for the message, status, and timestamp.
  • The global handler uses this model to return a consistent error structure.
Conclusion

Exception handling in Spring Boot is flexible and powerful.Spring Boot provides all the tools you need to build reliable and user-friendly APIs. By adopting best practices such as standardized error responses, you can ensure a consistent and professional experience for API consumers.

Question 34. What is the use of ResponseEntity in Spring Boot?

In Spring Boot, the ResponseEntity class is a powerful way to customize the HTTP response. It represents the entire HTTP response, including the status code, headers, and body. Using ResponseEntity, you can have full control over the HTTP response returned to the client, making it an essential tool for building robust REST APIs.

Why Use ResponseEntity?
  • Customizable Responses: Allows you to set HTTP status codes, headers, and body.
  • Error Handling: Enables sending specific error messages with appropriate status codes.
  • Flexibility: You can return any type of data (JSON, XML, plain text) in the response body.
  • Standardized API: Helps create APIs that adhere to RESTful principles.
Basic Usage of ResponseEntity

Example 1: Returning a Simple Response

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/greet")
    public ResponseEntity<String> greetUser() {
        return ResponseEntity.ok("Hello, Welcome to Spring Boot!");
    }
}
Explanation:
  • The ResponseEntity.ok() method is a shorthand for creating a response with the HTTP status 200 OK.
  • The response body contains the string “Hello, Welcome to Spring Boot!”.
Adding HTTP Headers

Example 2: Setting Custom Headers

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/headers")
    public ResponseEntity<String> customHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Custom-Header", "CustomHeaderValue");

        return ResponseEntity.ok()
                .headers(headers)
                .body("Response with custom headers");
    }
}
Explanation:
  • HttpHeaders is used to create and add custom headers.
  • The ResponseEntity.ok() method is used with the headers() method to include the custom headers in the response.
Returning Different HTTP Status Codes

Example 3: Returning 201 Created for Resource Creation

@RestController
@RequestMapping("/api")
public class DemoController {

    @PostMapping("/create")
    public ResponseEntity<String> createResource() {
        return ResponseEntity.status(HttpStatus.CREATED)
                .body("Resource created successfully!");
    }
}
Explanation:
  • ResponseEntity.status() is used to set a specific HTTP status code, in this case, 201 Created.
  • The response body confirms the creation of the resource.
Error Handling with ResponseEntity

Example 4: Returning an Error Response

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/error")
    public ResponseEntity<String> handleError() {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body("Invalid request parameters");
    }
}
Explanation:
  • HttpStatus.BAD_REQUEST is used to return an HTTP 400 Bad Request status code.
  • The response body provides additional details about the error.
Using ResponseEntity with Objects

Example 5: Returning JSON Data

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/user")
    public ResponseEntity<User> getUser() {
        User user = new User(1, "John Doe", "john.doe@example.com");
        return ResponseEntity.ok(user);
    }
}

class User {
    private int id;
    private String name;
    private String email;

    // Constructor, Getters, and Setters
    public User(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getters and Setters
}
Explanation:
  • The User object is serialized into JSON and sent as the response body.
  • The ResponseEntity.ok() method ensures the HTTP status is 200 OK.
Advanced Usage: Builder Pattern

ResponseEntity supports a builder pattern for more complex responses.

Example 6: Combining Status, Headers, and Body

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/complex")
    public ResponseEntity<String> complexResponse() {
        return ResponseEntity.status(HttpStatus.ACCEPTED)
                .header("Custom-Header", "HeaderValue")
                .body("Complex response with status, headers, and body");
    }
}
Explanation:
  • Combines HTTP status, headers, and body in a single response.
Key Benefits of ResponseEntity
  1. Comprehensive Control: You can control every aspect of the HTTP response.
  2. RESTful Compliance: Helps build APIs that follow REST principles by explicitly setting status codes and response details.
  3. Error Management: Simplifies returning appropriate error responses.
Conclusion

The ResponseEntity class is a versatile and powerful tool in Spring Boot for handling HTTP responses. By using it effectively, you can build APIs that are robust, flexible. Whether you’re setting custom headers, returning error messages, or sending JSON data, ResponseEntity makes it straightforward to manage responses in your application.

Question 35. How do you secure a REST API in Spring Boot?

Securing a REST API in Spring Boot is a critical step in ensuring that your application is protected from unauthorized access and malicious attacks. Spring Boot provides robust tools for implementing security, primarily through Spring Security, which is a powerful and customizable framework.

This tutorial will guide you step-by-step, from the basics to advanced concepts, on how to secure your REST API in Spring Boot.

Why Secure a REST API?
  • Prevent Unauthorized Access: Ensure only authenticated users can access your API.
  • Protect Sensitive Data: Encrypt and secure data transmitted between clients and servers.
  • Maintain Integrity: Prevent tampering with data during communication.
  • Ensure Accountability: Log and track user activities for compliance and auditing.
Step 1: Add Spring Security to Your Project

To secure your REST API, you need to include Spring Security in your project. Add the following dependency to your pom.xml if you’re using Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

For Gradle, add this to your build.gradle:

implementation 'org.springframework.boot:spring-boot-starter-security'
Step 2: Configure Basic Authentication

By default, Spring Security enables basic authentication. Once you add the dependency, your application will require a username and password for every endpoint.

Default Credentials

Spring Boot generates a default username (user) and a random password, which is displayed in the console when the application starts. Look for a line like this:

Using generated security password: <random-password>
Step 3: Customizing the Username and Password

To customize the default username and password, add the following properties to your application.properties file:

spring.security.user.name=admin
spring.security.user.password=admin123

Now, your API will require these credentials for authentication.

Step 4: Secure Specific Endpoints

You can configure which endpoints require authentication and which are publicly accessible. Create a SecurityConfig class to customize Spring Security settings:

Example: Configuring Endpoint Security

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeHttpRequests()
            .requestMatchers("/api/public/**").permitAll() // Public endpoints
            .anyRequest().authenticated() // All other endpoints require authentication
            .and()
            .httpBasic(); // Enables basic authentication

        return http.build();
    }
}
Explanation:
  • csrf().disable(): Disables CSRF protection (use cautiously; enable it if needed).
  • requestMatchers("/api/public/**").permitAll(): Allows public access to specific endpoints.
  • anyRequest().authenticated(): Requires authentication for all other endpoints.
  • httpBasic(): Enables basic authentication.
Step 5: Use Role-Based Authorization

You can assign roles to users and restrict access based on roles.

Example: Role-Based Authorization

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeHttpRequests()
        .requestMatchers("/api/admin/**").hasRole("ADMIN") // Only ADMIN role can access
        .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") // USER or ADMIN roles can access
        .anyRequest().authenticated()
        .and()
        .httpBasic();

    return http.build();
}
Step 6: Implement JWT Authentication (Advanced)

For stateless authentication, you can use JSON Web Tokens (JWT). JWT allows secure, stateless communication between clients and servers.

Add Dependencies

Add the following dependencies to your pom.xml:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
Generate JWT Tokens

Create a utility class to generate and validate JWT tokens:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtUtil {
    private final String SECRET_KEY = "mysecretkey";

    public String generateToken(String username) {
        return Jwts.builder()
                   .setSubject(username)
                   .setIssuedAt(new Date())
                   .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
                   .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                   .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
    }
}
Secure Endpoints with JWT

Update your SecurityConfig to include JWT filters.

Step 7: Testing Your Secure API

Use tools like Postman or cURL to test your secured endpoints.

Testing Basic Authentication
curl -u admin:admin123 http://localhost:8080/api/private

Testing JWT Authentication

  • Obtain a JWT token by calling the login endpoint.
  • Use the token in the Authorization header for subsequent requests.
Conclusion

Securing a REST API in Spring Boot involves several layers, from basic authentication to advanced JWT-based stateless authentication. By following the steps outlined in this tutorial, you can ensure that your API is protected and adheres to best practices for security.

Question 36. What is CORS, and how do you enable it in Spring Boot?

CORS (Cross-Origin Resource Sharing) is a security feature implemented by web browsers to prevent unauthorized access to resources on a server from a different domain. When a client (e.g., a web application running on http://example.com) tries to access resources from a server hosted on another domain (e.g., http://api.example.com), the browser enforces CORS policies to determine whether the request is allowed.

Why is CORS Important?
  • Security: Prevents unauthorized access to resources from malicious domains.
  • Control: Allows servers to define which origins (domains) are permitted to access their resources.
  • Flexibility: Enables legitimate cross-origin requests, such as those required by Single Page Applications (SPAs) consuming APIs.
How Does CORS Work?

When a browser sends a cross-origin request, it performs a CORS preflight check for certain types of requests. This involves sending an HTTP OPTIONS request to the server to verify:

  • The allowed HTTP methods (GET, POST, etc.).
  • The allowed headers (Authorization, Content-Type, etc.).
  • Whether credentials (cookies or HTTP authentication) can be included.

If the server responds with appropriate CORS headers, the browser allows the request. Otherwise, it blocks the request.

Enabling CORS in Spring Boot

Spring Boot makes it simple to configure CORS for your REST APIs. Let’s explore various ways to enable CORS, starting from basic configurations to more advanced use cases.

1. Enable CORS Globally

To enable CORS for all endpoints in your application, you can define a CorsConfiguration bean.

Example: Global CORS Configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://example.com"); // Allow specific origin
        config.addAllowedOrigin("http://another-domain.com"); // Allow another origin
        config.addAllowedMethod("*"); // Allow all HTTP methods (GET, POST, PUT, DELETE, etc.)
        config.addAllowedHeader("*"); // Allow all headers
        config.setAllowCredentials(true); // Allow cookies or credentials

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config); // Apply to all endpoints
        return new CorsFilter(source);
    }
}
Explanation:
  • addAllowedOrigin: Specifies which domains can access the API.
  • addAllowedMethod: Specifies which HTTP methods are allowed (e.g., GET, POST).
  • addAllowedHeader: Specifies which headers the client can include in the request.
  • setAllowCredentials: Enables cookies or authentication headers in cross-origin requests.
2. Enable CORS for Specific Endpoints

You can configure CORS at the controller or method level using the @CrossOrigin annotation.

Example: CORS for Specific Endpoints

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @CrossOrigin(origins = "http://example.com", methods = {RequestMethod.GET, RequestMethod.POST})
    @GetMapping("/data")
    public String getData() {
        return "CORS-enabled endpoint";
    }
}
Explanation:
  • @CrossOrigin: Enables CORS for the specific endpoint.
  • origins: Specifies the allowed domains.
  • methods: Specifies the allowed HTTP methods.
3. Enable CORS Using Spring Security

If your application uses Spring Security, you need to explicitly configure CORS in the security configuration.

Example: CORS with Spring Security

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors().and()
            .csrf().disable()
            .authorizeHttpRequests()
            .anyRequest().authenticated();

        return http.build();
    }
}

Additionally, define a CorsConfigurationSource bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.cors.CorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://example.com");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}
5.Testing CORS

To test your CORS configuration:

  1. Use tools like Postman to make cross-origin requests.
  2. Open the browser console and check for CORS errors when making requests from a different domain.

Example: Using cURL

curl -X GET -H "Origin: http://example.com" http://localhost:8080/api/data
Common Issues and Troubleshooting
  1. CORS Preflight Requests Failing:
    • Ensure the server responds to OPTIONS requests.
    • Configure addAllowedMethod("OPTIONS").
  2. Credentials Not Working:
    • Set setAllowCredentials(true) on the server.
    • Ensure the client includes credentials in the request.
  3. Wildcard in Allowed Origins:
    • Avoid using "*" for addAllowedOrigin if setAllowCredentials(true) is enabled.
Conclusion

CORS is an essential aspect of securing and enabling cross-origin communication in modern web applications. By leveraging Spring Boot’s flexible configuration options, you can easily control which domains can access your API, ensuring both security and usability.

Whether you’re a beginner or an advanced developer, understanding and implementing CORS is a critical skill for building robust REST APIs.

Question 37. How do you test REST APIs in Spring Boot?

Testing REST APIs in Spring Boot is essential to ensure that your application behaves as expected and meets all functional requirements. Spring Boot provides a robust testing framework, making it easier to write and execute tests for your RESTful APIs. Lets explore testing REST APIs from beginner -level concepts to advanced techniques.

1. Understanding the Testing Framework in Spring Boot

Spring Boot supports several testing tools and libraries, including:

  • JUnit: The most popular Java testing framework.
  • Spring Boot Test: Provides features to test Spring Boot applications.
  • MockMvc: A class provided by Spring to test controllers without starting the full application.
  • RestTemplate: Useful for integration testing by making actual HTTP calls.
  • Testcontainers: For advanced integration testing with real databases.
2. Types of Tests for REST APIs
  • Unit Testing: Focuses on testing individual components (e.g., controllers or services) in isolation.
  • Integration Testing: Verifies that the components work together, often involving the database or external services.
  • End-to-End Testing: Simulates real-world usage by testing the entire application stack.
3. Setting Up Your Testing Environment

Before writing tests, ensure you have the necessary dependencies in your pom.xml file (for Maven projects):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Note : This dependency includes JUnit, Mockito, and other testing utilities.

4. Writing Unit Tests for Controllers

Unit tests ensure that your REST API endpoints behave correctly

Example – Unit test using MockMvc

Controller Code
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping("/{id}")
    public ResponseEntity<String> getBookById(@PathVariable int id) {
        if (id == 1) {
            return ResponseEntity.ok("Spring Boot with DigiTech GenAI");
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}
Unit Test Code
@WebMvcTest(BookController.class)
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetBookById_Found() throws Exception {
        mockMvc.perform(get("/api/books/1"))
               .andExpect(status().isOk())
               .andExpect(content().string("Spring Boot with DigiTech GenAI"));
    }

    @Test
    public void testGetBookById_NotFound() throws Exception {
        mockMvc.perform(get("/api/books/2"))
               .andExpect(status().isNotFound());
    }
}
Explanation:
  • @WebMvcTest: Loads only the web layer for testing.
  • MockMvc: Simulates HTTP requests and verifies responses.
  • perform(): Executes a mock HTTP request.
  • andExpect(): Verifies the response status and content.
5. Writing Integration Tests

Integration tests validate the interaction between different layers, such as controllers and databases.

Controller Code with Database
@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/{id}")
    public ResponseEntity<Book> getBookById(@PathVariable Long id) {
        return bookService.getBookById(id)
                          .map(ResponseEntity::ok)
                          .orElse(ResponseEntity.notFound().build());
    }
}
Integration Test Code
@SpringBootTest
@AutoConfigureMockMvc
public class BookControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private BookRepository bookRepository;

    @BeforeEach
    public void setup() {
        bookRepository.save(new Book(1L, "Spring Boot with DigiTech GenAI"));
    }

    @AfterEach
    public void cleanup() {
        bookRepository.deleteAll();
    }

    @Test
    public void testGetBookById_Found() throws Exception {
        mockMvc.perform(get("/api/books/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.title").value("Spring Boot with DigiTech GenAI"));
    }
}
Explanation:
  • @SpringBootTest: Loads the entire application context.
  • @AutoConfigureMockMvc: Configures MockMvc for integration testing.
  • jsonPath(): Verifies JSON fields in the response.
6. Advanced Testing with Testcontainers

For realistic integration testing, you can use Testcontainers to spin up a real database in a Docker container.

Example with PostgreSQL
@Testcontainers
@SpringBootTest
public class BookRepositoryTest {

    @Container
    public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:latest")
        .withDatabaseName("testdb")
        .withUsername("testuser")
        .withPassword("testpass");

    @Autowired
    private BookRepository bookRepository;

    @Test
    public void testSaveBook() {
        Book book = new Book(null, "Spring Boot with DigiTech GenAI");
        Book savedBook = bookRepository.save(book);
        assertNotNull(savedBook.getId());
    }
}
Explanation:
  • @Testcontainers: Enables Testcontainers in your test.
  • PostgreSQLContainer: Spins up a PostgreSQL instance in Docker.
  • The test runs against a real database, providing more accurate results.
7.Tips for Effective Testing
  • Use meaningful test names that describe the scenario.
  • Mock external dependencies using Mockito.
  • Keep tests isolated and independent.
  • Write tests for edge cases, such as invalid inputs or server errors.
  • Measure test coverage to ensure critical code paths are tested.
Conclusion

Testing REST APIs in Spring Boot is a crucial part of the development process. By leveraging tools like MockMvc, RestTemplate, and Testcontainers, you can write comprehensive tests that cover all aspects of your application. Start with unit tests to validate individual components, then move to integration and end-to-end tests for a complete evaluation. With these practices, you can ensure your APIs are robust, reliable, and ready for production use.

Question 38. How do you implement versioning in a Spring Boot REST API?

Versioning is a critical aspect of designing RESTful APIs, especially as your application evolves and new features are added. It allows you to maintain backward compatibility while introducing changes, ensuring older clients can still use the API without breaking.

Let’s explore different ways to implement versioning in a Spring Boot REST API. We’ll start with basic concepts and move to advanced techniques.

1. Why is Versioning Important?

APIs are consumed by various clients, such as web applications, mobile apps, and third-party integrations. When you update your API, older clients might not be compatible with the changes. Versioning provides a way to:

  • Introduce new features or changes without disrupting existing clients.
  • Maintain backward compatibility for older versions.
  • Allow clients to migrate to newer versions at their own pace.
2. Approaches to API Versioning

There are several ways to version a REST API:

  1. URI Versioning (Path-based)
  2. Query Parameter Versioning
  3. Header Versioning
  4. Media Type Versioning (a.k.a. Content Negotiation)

Let’s implement each of these approaches step by step.

3. URI Versioning

In this method, the version is included as part of the URL path. This is the simplest and most commonly used approach.

Implementation
@RestController
@RequestMapping("/api/v1/books")
public class BookControllerV1 {

    @GetMapping
    public List<String> getAllBooks() {
        return List.of("Book 1", "Book 2", "Book 3");
    }
}

@RestController
@RequestMapping("/api/v2/books")
public class BookControllerV2 {

    @GetMapping
    public List<String> getAllBooks() {
        return List.of("Advanced Book 1", "Advanced Book 2");
    }
}
Explanation:
  • /api/v1/books: Represents version 1 of the API.
  • /api/v2/books: Represents version 2 of the API with updated functionality.
Pros:
  • Easy to implement and understand.
  • Explicit versioning in the URL.
Cons:
  • May lead to cluttered URLs if many versions exist.
4. Query Parameter Versioning

In this method, the version is passed as a query parameter.

Implementation
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping
    public List<String> getBooksByVersion(@RequestParam(value = "version", defaultValue = "1") String version) {
        if ("2".equals(version)) {
            return List.of("Advanced Book 1", "Advanced Book 2");
        }
        return List.of("Book 1", "Book 2", "Book 3");
    }
}
Explanation:
  • /api/books?version=1: Fetch version 1 data.
  • /api/books?version=2: Fetch version 2 data.
Pros:
  • No need to change the URL structure.
  • Flexible for clients to specify the version dynamically.
Cons:
  • Versioning logic can become complex in the controller.
5. Header Versioning

In this approach, the version is specified in the request header

Implementation
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping
    public List<String> getBooksByHeader(@RequestHeader(value = "API-Version", defaultValue = "1") String version) {
        if ("2".equals(version)) {
            return List.of("Advanced Book 1", "Advanced Book 2");
        }
        return List.of("Book 1", "Book 2", "Book 3");
    }
}
Explanation:
  • Add a custom header API-Version in the request:
    • API-Version: 1
    • API-Version: 2
Pros:
  • Keeps URLs clean.
  • Suitable for APIs used by controlled clients, like mobile apps.
Cons:
  • Harder to test manually using a browser.
  • Requires clients to set custom headers.
6. Media Type Versioning (Content Negotiation)

Here, the version is included in the Accept header as part of the media type.

Implementation
@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping(produces = "application/vnd.example.v1+json")
    public List<String> getBooksV1() {
        return List.of("Book 1", "Book 2", "Book 3");
    }

    @GetMapping(produces = "application/vnd.example.v2+json")
    public List<String> getBooksV2() {
        return List.of("Advanced Book 1", "Advanced Book 2");
    }
}
Explanations:
  • Set the Accept header in the request:
    • Accept: application/vnd.example.v1+json
    • Accept: application/vnd.example.v2+json
Pros:
  • Allows versioning based on content type.
  • Suitable for APIs with complex response formats.
Cons:
  • Harder to implement and understand.
  • Testing requires tools like Postman or curl.
7. Advanced Techniques: Using a Versioning Strategy

You can centralize versioning logic using a custom HandlerMapping.

Example: Centralized Versioning Strategy

@Configuration
public class ApiVersioningConfig {

    @Bean
    public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new VersionedRequestMappingHandlerMapping();
            }
        };
    }
}

public class VersionedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return AnnotationUtils.findAnnotation(beanType, RestController.class) != null;
    }

    @Override
    protected String getMappingForMethod(Method method, Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        if (apiVersion != null) {
            return apiVersion.value();
        }
        return super.getMappingForMethod(method, handlerType);
    }
}
Explanation:
  • Define a custom annotation like @ApiVersion.
  • Implement logic to route requests based on the annotation.
8. Choosing the Right Approach
ApproachBest ForExample Use Case
URI VersioningSimple APIs with fewer versionsPublic APIs with clear versioning
Query ParameterDynamic version selectionInternal APIs
Header VersioningClean URLsAPIs for mobile apps or controlled clients
Media Type VersioningAdvanced content negotiationComplex APIs with varying response formats
Conclusion

Versioning is a vital part of building scalable and maintainable REST APIs in Spring Boot. Depending on your use case, you can choose from URI versioning, query parameters, headers, or media type versioning. By implementing proper versioning, you ensure your APIs remain robust, flexible, and client-friendly.

Question 39. How do you handle file uploads in Spring Boot?

Uploading files is a common requirement in web applications. Spring Boot provides robust support for handling file uploads using Spring MVC and the MultipartFile interface. Let’s walk through a step-by-step guide to implementing file uploads in a Spring Boot application.

1. Setting Up the Project

Create a Spring Boot project with the following dependencies:

  • Spring Web (for building REST APIs)
  • Spring Boot Starter Thymeleaf (optional, for handling file uploads via a frontend)
  • Spring Boot Starter Test (for testing file uploads)

For Maven, add these dependencies to your pom.xml:

<dependencies>
    <!-- Spring Boot Web dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Starter Thymeleaf (for frontend, optional) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!-- Spring Boot Test (for testing) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
2. Configuring File Upload Settings

Spring Boot has built-in support for file uploads, but you can customize settings in application.properties:

# Maximum file size allowed (10MB)
spring.servlet.multipart.max-file-size=10MB
# Maximum request size allowed (20MB)
spring.servlet.multipart.max-request-size=20MB

Note: This ensures that large files do not exceed the allowed limit

3. Create a REST API for File Upload

Create a Spring Boot controller to handle file uploads using the MultipartFile interface

Controller Implementation
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;

@RestController
@RequestMapping("/api/files")
public class FileUploadController {

    private static final String UPLOAD_DIR = "uploads/";

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please select a file to upload.");
        }

        try {
            // Ensure the directory exists
            Files.createDirectories(Paths.get(UPLOAD_DIR));

            // Define the file path
            Path filePath = Paths.get(UPLOAD_DIR + file.getOriginalFilename());

            // Save the file to the defined path
            Files.write(filePath, file.getBytes());

            return ResponseEntity.ok("File uploaded successfully: " + file.getOriginalFilename());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("File upload failed: " + e.getMessage());
        }
    }
}

Explanation

  • The method accepts a file via @RequestParam("file") MultipartFile file.
  • It validates if a file is selected.
  • It creates the uploads directory if it does not exist.
  • It writes the file to the uploads/ folder using Files.write().
  • It returns an appropriate response based on success or failure.
4. Creating a Frontend for File Upload (Optional)

If you want to test this with a UI, create an HTML page using Thymeleaf:

file_upload.html
<!DOCTYPE html>
<html>
<head>
    <title>File Upload</title>
</head>
<body>
    <h2>Upload a File</h2>
    <form method="POST" enctype="multipart/form-data" action="/api/files/upload">
        <input type="file" name="file" required>
        <button type="submit">Upload</button>
    </form>
</body>
</html>
Explanation
  • The form uses multipart/form-data encoding.
  • The input field allows users to select a file.
  • The form submits the file to /api/files/upload.
5. Testing the File Upload API

You can test the file upload feature using Postman or cURL.

Using Postman
  • Open Postman.
  • Select POST request.
  • Enter the API URL: http://localhost:8080/api/files/upload.
  • In the Body section, choose form-data
  • .Add a key as file and select a file from your system.
  • Click Send.
Using cURL

Run the following command:

curl -X POST -F "file=@/path/to/your/file.jpg" http://localhost:8080/api/files/upload

If successful, the response should be:

File uploaded successfully: file.jpg
6. Handling Multiple File Uploads

If you want to upload multiple files at once, modify your controller method like this:

@PostMapping("/upload-multiple")
public ResponseEntity<String> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
    if (files.length == 0) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please select at least one file.");
    }

    for (MultipartFile file : files) {
        try {
            Path filePath = Paths.get(UPLOAD_DIR + file.getOriginalFilename());
            Files.write(filePath, file.getBytes());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to upload: " + file.getOriginalFilename());
        }
    }
    return ResponseEntity.ok("All files uploaded successfully.");
}
Frontend Form for Multiple Uploads
<form method="POST" enctype="multipart/form-data" action="/api/files/upload-multiple">
    <input type="file" name="files" multiple required>
    <button type="submit">Upload Files</button>
</form>
8. Store Files in a Database (Advanced)

Instead of storing files in the file system, you can save them in a database as BLOBs (Binary Large Objects).

Entity Class
import jakarta.persistence.*;

@Entity
public class FileEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String fileName;

    @Lob
    private byte[] fileData;

    // Getters and Setters
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;

public interface FileRepository extends JpaRepository<FileEntity, Long> {
}
Service for Saving File Data
@Service
public class FileService {
    @Autowired
    private FileRepository fileRepository;

    public FileEntity storeFile(MultipartFile file) throws IOException {
        FileEntity fileEntity = new FileEntity();
        fileEntity.setFileName(file.getOriginalFilename());
        fileEntity.setFileData(file.getBytes());
        return fileRepository.save(fileEntity);
    }
}
8. Conclusion
  • Spring Boot makes file uploads simple with MultipartFile.
  • You can store files in the file system or database.
  • You can use tools like Postman and cURL to test APIs.
  • You can expand this by adding file validation, size restrictions, and security measures.

Question 40. What is the use of Swagger in Spring Boot?

When developing RESTful APIs in Spring Boot, documentation is essential to help developers understand how to interact with your API. Swagger is a powerful tool that simplifies API documentation and testing.

In this tutorial, we’ll go from beginner to advanced, covering:

  • What Swagger is and why it’s useful
  • How to integrate Swagger into a Spring Boot project
  • How to customize and secure Swagger
1. What is Swagger?
  • Automatic API documentation with detailed endpoints.
  • An interactive UI where developers can test APIs directly in the browser.
  • Code generation support for various programming languages
Why Use Swagger?
  • Reduces manual API documentation effort.
  • Makes API testing easy with an interactive UI.
  • Improves collaboration between frontend and backend teams.
  • Helps generate API clients in different languages
2. Setting Up Swagger in a Spring Boot Project

To use Swagger in a Spring Boot project, follow these steps.

Step 1: Add Dependencies

For Maven, add the following dependency to pom.xml:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.0.2</version>
</dependency>

For Gradle, add this to build.gradle:

dependencies {
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
}
Step 2: Create a Simple REST API
controller
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Arrays;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping
    public List<String> getAllProducts() {
        return Arrays.asList("Laptop", "Smartphone", "Tablet");
    }

    @GetMapping("/{id}")
    public String getProductById(@PathVariable int id) {
        List<String> products = Arrays.asList("Laptop", "Smartphone", "Tablet");
        return products.get(id);
    }
}
Step 3: Access Swagger UI

Once the application is running, open Swagger UI in your browser:

http://localhost:8080/swagger-ui.html
3. Customizing Swagger

By default, Swagger scans all REST APIs, but you can customize its behavior.

Step 1: Create an API Configuration Class

You can customize API documentation using @OpenAPIDefinition.

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.context.annotation.Configuration;

@Configuration
@OpenAPIDefinition(
    info = @Info(
        title = "Product API",
        version = "1.0",
        description = "API for managing products"
    )
)
public class SwaggerConfig {
}

Note: This will change the title, version, and description of your API.

Step 2: Adding API Descriptions

By use Swagger annotations to provide more details.

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Arrays;

@RestController
@RequestMapping("/api/products")
@Tag(name = "Product API", description = "Operations related to products")
public class ProductController {

    @Operation(summary = "Get all products", description = "Fetches a list of all available products")
    @GetMapping
    public List<String> getAllProducts() {
        return Arrays.asList("Laptop", "Smartphone", "Tablet");
    }

    @Operation(summary = "Get product by ID", description = "Fetches details of a single product by its ID")
    @GetMapping("/{id}")
    public String getProductById(@PathVariable int id) {
        List<String> products = Arrays.asList("Laptop", "Smartphone", "Tablet");
        return products.get(id);
    }
}

Note : Now, when you open Swagger UI, you’ll see detailed documentation for each endpoint.

4. Securing Swagger

In production, you might want to restrict Swagger access to only admins or disable it completely.

Option 1: Restrict Access to Swagger

You can configure Spring Security to restrict access to the Swagger UI.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .formLogin()
        .and()
        .logout();
        return http.build();
    }
}

Now, only users with the ADMIN role can access Swagger.

Option 2: Disable Swagger in Production

To disable Swagger in production, modify application.properties:

springdoc.api-docs.enabled=false
springdoc.swagger-ui.enabled=false

Or disable it programmatically:

@Configuration
public class SwaggerDisableConfig {
    @Bean
    public GroupedOpenApi disableSwagger() {
        return GroupedOpenApi.builder()
                .group("hidden")
                .pathsToExclude("/v3/api-docs/**", "/swagger-ui/**")
                .build();
    }
}
5. Generating API Documentation as JSON or YAML

Swagger allows you to generate API specs in JSON or YAML format.

JSON format:
http://localhost:8080/v3/api-docs
YAML format:
http://localhost:8080/v3/api-docs.yaml

Note : This is useful for generating client SDKs or sharing API specs.

Conclusion

Swagger simplifies API documentation and testing in Spring Boot.

  • Automatically generates REST API documentation
  • Provides an interactive UI for testing APIs
  • Allows customization using annotations
  • Can be secured in production
  • Supports JSON/YAML exports for integration with other tools

Series Navigation<< Top 10 Questions on Spring Boot Data Handling with AnswersTop 10 Questions on Spring Boot Security with Answers >>

Leave a Reply

Your email address will not be published. Required fields are marked *