Spring Security for API Rate Limiting
API rate limiting in Spring Security is an essential technique for modern web applications to control client requests to a particular endpoint over a specified time frame. This practice helps protect APIs from abuse, ensures fair resource usage, and prevents Denial of Service (DoS) attacks by controlling traffic to backend services.
In this article, we will explore implementing API rate limiting using Spring Security with a custom filter and an optional Redis-based solution for distributed environments.
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 {
// A thread-safe map to store the number of requests per client IP
private final Map<String, Integer> requestCounts = new ConcurrentHashMap<>();
// Define maximum allowed requests per minute
private static final int MAX_REQUESTS_PER_MINUTE = 5;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Get the client's IP address
String clientIp = request.getRemoteAddr();
// Initialize the count if the IP is new, otherwise get the current count
requestCounts.putIfAbsent(clientIp, 0);
int requestCount = requestCounts.get(clientIp);
// If the count exceeds the limit, return a 429 Too Many Requests response
if (requestCount >= MAX_REQUESTS_PER_MINUTE) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests - please try again later.");
return;
}
// Otherwise, increment the request count and proceed with the request
requestCounts.put(clientIp, requestCount + 1);
filterChain.doFilter(request, response);
}
}
- This class extends
OncePerRequestFilter
, meaning the filter is executed once per request. - The map
requestCounts
tracks 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 Requests
response. - 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
GET
endpoint (/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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://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:run
Output:

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/test
Postman 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.