API rate limiting in Spring Security is used to control how many requests a client can make to an API within a specific time period. It helps protect applications from abuse, excessive traffic, and Denial of Service (DoS) attacks. By integrating rate limiting with Spring Security filters, requests can be monitored and restricted efficiently.
- Controls the number of API requests per user/IP within a time window.
- Helps prevent abuse, spam requests, and DoS attacks.
- Can be implemented using in-memory counters, Redis, or libraries like Bucket4j.
Prerequisites:
- Basic knowledge of the Java and Spring Framework.
- Familiarity with concepts like filters and authentication.
- Maven for building dependency management.
- JDK and IntelliJ IDEA installed in your system
API Rate Limiting
API rate limiting helps manage traffic by setting the maximum number of requests a user can make to the API within a given time period. For instance, we might restrict users to 100 requests per minute to prevent overuse. When the user exceeds this limit, the server responds with a 429 Too Many Requests status.
Common Approaches to Rate Limiting:
- In-Memory Counters: Suitable for simple use cases in a single-instance application.
- Distributed Rate Limiting with Redis: Suitable for applications running across multiple instances.
- Libraries like Bucket4j: For more advanced rate-limiting scenarios.
Implementing API Rate Limiting with Spring Boot and Spring Security
This example demonstrates how to implement API rate limiting in a Spring Boot application using Spring Security. We'll use an in-memory rate-limiting mechanism in this example.
Step 1: Create a New Spring Boot Project
Create a new Spring Boot project using IntelliJ IDEA and select the following options:
- Name:
rate-limiting-demo - Language: Java
- Type: Maven
- Packaging: Jar
Click Next, then select the necessary dependencies.

Step 2: Add the Dependencies
Add the following dependencies into the Spring Boot Project.
- Spring Web
- Spring Boot DevTools
- Lombok
- Spring Security
- Spring Data Redis
Click on the Create button.

Project Structure
After the project creation done, set the file structure as shown in the below image:

Step 3: Configure Application Properties
In the application.properties file, configure the application and Redis settings (if needed for distributed environments):
# Application settings
spring.application.name=api-rate-limiting
server.port=8080
# Redis configuration (if using Redis for distributed rate limiting)
spring.data.redis.host=localhost
spring.data.redis.port=6379
Step 4: Creating the Rate Limiting Filter
We will create a custom filter that tracks and limits requests based on the client's IP address.
RateLimitingFilter.java:
package com.gfg.apiratelimiting;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RateLimitingFilter extends OncePerRequestFilter {
// Class to store request count + timestamp
private static class RequestInfo {
int count;
long timestamp;
RequestInfo(int count, long timestamp) {
this.count = count;
this.timestamp = timestamp;
}
}
// Store per-IP request data
private final Map<String, RequestInfo> requestCounts = new ConcurrentHashMap<>();
private static final int MAX_REQUESTS = 5;
private static final long TIME_WINDOW = 60 * 1000; // 1 minute
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String clientIp = request.getRemoteAddr();
long currentTime = System.currentTimeMillis();
requestCounts.putIfAbsent(clientIp, new RequestInfo(0, currentTime));
RequestInfo info = requestCounts.get(clientIp);
// Reset counter if time window passed
if (currentTime - info.timestamp > TIME_WINDOW) {
info.count = 0;
info.timestamp = currentTime;
}
// Block if limit exceeded
if (info.count >= MAX_REQUESTS) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests - please try again later.");
return;
}
// Increment count
info.count++;
filterChain.doFilter(request, response);
}
}
- This class extends
OncePerRequestFilter, meaning the filter is executed once per request. - The map
requestCountstracks the number of requests for each client (based on IP). - When the request count exceeds the defined
MAX_REQUESTS_PER_MINUTE, it returns an HTTP429 Too Many Requestsresponse. - If the limit is not exceeded, the request proceeds as usual.
Step 5: Registering the Rate Limiting Filter in Security Configuration
To apply the rate-limiting filter to your API, we need to register it in the security configuration.
SecurityConfig.java:
package com.gfg.apiratelimiting;
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 {
// Define the security filter chain
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, RateLimitingFilter rateLimitingFilter) throws Exception {
// Disable CSRF for simplicity and allow all requests
http.csrf().disable()
.authorizeHttpRequests()
.anyRequest().permitAll()
.and()
// Register the rate-limiting filter before the UsernamePasswordAuthenticationFilter
.addFilterBefore(rateLimitingFilter, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
- The security configuration disables CSRF (Cross-Site Request Forgery) for simplicity in this example.
- The custom rate-limiting filter (
RateLimitingFilter) is registered before theUsernamePasswordAuthenticationFilter, meaning it will intercept every request before authentication.
Step 6: Create a Controller Class for Testing
We'll create a simple API endpoint to test the rate limiting.
ApiController.java:
package com.gfg.apiratelimiting;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
// A simple GET endpoint for testing rate limiting
@GetMapping("/api/test")
public String testApi() {
return "API is working!";
}
}
- This controller defines a simple
GETendpoint (/api/test) that returns a success message. - We will use this endpoint to test the rate-limiting functionality.
pom.xml File
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gfg</groupId>
<artifactId>api-rate-limiting</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-rate-limiting</name>
<description>api-rate-limiting</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 7: Run the Application
Once the project is complete, run the Spring Boot application on port 8080 using the following command:
mvn spring-boot:runOutput:

Step 8: Testing the Rate Limiting
We can use Postman to test the API rate limiting. Here is how the responses should behave:
First 5 requests: The API will respond with a 200 OK status and the message "API is working!".
GET http://localhost:8080/api/testPostman showing the successful API requests until the rate limit can be reached.
Response:

6th request and beyond (within a minute): The API will respond with a 429 Too Many Requests status and the message "Too many requests - please try again later."
Response:

In this example project, we successfully implemented the simple API rate limiting solution in the Spring Boot using the Spring Security.