# spring-cloud-zuul-ratelimit, per-service limits in front of a Netflix Zuul gateway

> This module adds rate limiting to Spring Cloud Netflix Zuul, with a configurable storage backend for the counters and a policy list that can be applied globally or per downstream service. The interesting part is not the limiting but the eight key strategies you can choose from, and the option to combine them.

**marcosbarbero/spring-cloud-zuul-ratelimit** — Rate limit auto-configure for Spring Cloud Netflix Zuul

- Repository: https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
- Website: https://blog.marcosbarbero.com/spring-cloud-netflix-zuul-rate-limit/
- Stars: 1,130 · Forks: 384
- Language: Java
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/marcosbarbero-spring-cloud-zuul-ratelimit

## A Zuul filter and a storage backend, plus a policy per service

The module is described in one line: a module to enable rate limit per service in Netflix Zuul. The per service part is the point. A gateway that fronts many downstream services usually needs different limits for different services, and a single global limit is a blunt instrument that either lets a cheap internal service through unlimited or throttles an expensive one for everyone.

The architecture is three pieces. A filter in the Zuul request path that decides whether a request is allowed, a storage backend that holds the counters, and a configuration that says which policy applies to which service.

The storage is pluggable, and the choices are the interesting part because they cover different deployment realities. Redis, which is the default in the sample configuration. Consul, for teams already running one. Spring Data JPA, which is the choice for teams with a relational database and no other shared store. And the Bucket4j family, which is a different shape entirely: bucket4j-core with bucket4j-jcache and the cache-api for a plain JCache provider, then bucket4j-hazelcast, bucket4j-infinispan and bucket4j-ignite for the distributed in-memory caches. The Bucket4j variants depend on the JCache variant, so you add both.

The JPA option needs a table, and the README gives the script:

```sql
CREATE TABLE rate (
  rate_key VARCHAR(255) NOT NULL,
  remaining BIGINT,
  remaining_quota BIGINT,
  reset BIGINT,
  expiration TIMESTAMP,
  PRIMARY KEY(rate_key)
);
```

The columns tell you the model. A rate_key identifying the counter, the remaining request count, the remaining quota in seconds, the reset timestamp, and the expiration, so a stale row can be reclaimed. That is a token bucket or a fixed window with a time component, which is what the refresh-interval setting in the configuration refers to.

The project is licensed Apache-2.0, the last push was on 2026-10-16, and the release tags are v2.4.3.RELEASE from 2021-10-26, v2.4.2.RELEASE from 2020-10-26 and v1.7.6.RELEASE from 2020-09-06.

## Eight ways to build a rate limit key, and they compose

The feature list is where this module differs from a plain token bucket, because the key you rate limit on is a choice, and there are eight of them.

Authenticated User uses the authenticated username or 'anonymous'. Request Origin uses the origin of the user request. URL uses the request path of the downstream service. URL Pattern uses the Ant path pattern to the downstream service. ROLE uses the authenticated user roles. Request method uses the HTTP request method. Request header uses the HTTP request header. And a global configuration per service does not validate the request Origin, Authenticated User or URI at all, and is used simply by not setting the type parameter.

That last one is a good design decision to notice. A global per-service limit needs no key derivation, so it applies to every request to that service regardless of who sent it, which is the right behaviour for a per-service overall cap.

The strategies can be combined, and the README states that it is possible to combine Authenticated User, Request Origin, URL, ROLE and Request Method just by adding multiple values to the list. That is the difference between a per-user limit and a per-user limit on a per-method-per-URL basis, and it is a single list in the configuration rather than a different filter for each combination.

The sample configuration shows the type list in a policy:

```yaml
type: #optional
  - user
  - origin
  - url
  - http_method
```

And the per-policy type syntax, where each entry names a strategy and optionally a value for it, so a policy can target a specific anonymous user, a specific origin, a URL prefix, a role, a method or a header:

```yaml
- type: #optional value for each type
  - user=anonymous
  - origin=somemachine.com
  - url=/api #url prefix
  - role=user
  - http_method=get #case insensitive
  - http_header=customHeader
```

The url_pattern=/api/*/payment form is a value applied to the URL Pattern strategy, and it is the one that lets you limit one endpoint differently from the rest of a service.

## Configuring a policy, and the two limits in each one

A policy has three numbers and a type list, and the two limits are easy to confuse.

The limit is the request number limit per refresh interval window. The quota is the request time limit per refresh interval window in seconds. The refresh-interval is the window itself, defaulting to 60 seconds. So a policy with limit 10 and quota 1000 and refresh-interval 60 allows ten requests in each sixty second window, and separately caps the total time spent on those requests at a thousand seconds in the same window.

The distinction matters for the endpoints where each request is expensive. A download endpoint with a high limit and a tight quota lets many users start a download and stops the ones that take too long, which is a different failure mode from a search endpoint with a low limit and a generous quota.

The configuration nests these in two places. default-policy-list applies unless a specific policy exists, and policy-list keys on the service identifier:

```yaml
default-policy-list: #optional - will apply unless specific policy exists
  - limit: 10
    quota: 1000
    refresh-interval: 60
policy-list:
  myServiceId:
    - limit: 10
      quota: 1000
      refresh-interval: 60
```

The global settings around them are the other half of the behaviour. key-prefix namespaces the counters in the storage, which matters when several Zuul instances or several environments share one Redis. enabled turns the filter on. repository selects the backend. behind-proxy tells the filter to read the client address from the X-Forwarded-For header rather than the socket, which is wrong if you set it without a proxy in front and right if you omit it when there is one. add-response-headers puts the limit information in the response so a client can see where it stands.

The deny-request block is a hard deny list, separate from the rate limits:

```yaml
deny-request:
  response-status-code: 404 #default value is 403 (FORBIDDEN)
  origins:
    - 200.187.10.25
    - somedomain.com
```

The status code is configurable with a default of 403, which is a sensible default for a gateway that is refusing on policy grounds.

The same configuration is available as properties, which matters when the values come from a config server:

```properties
zuul.ratelimit.enabled=true
zuul.ratelimit.key-prefix=your-prefix
zuul.ratelimit.repository=REDIS
zuul.ratelimit.behind-proxy=true
zuul.ratelimit.add-response-headers=true
```

The property form is where the type list becomes indexed, with zuul.ratelimit.default-policy-list[0].type[0]=user and type[1]=origin, which is the properties syntax for a list of strings.

## The version matrix is the thing to check first

The README carries two version notes that will decide whether this works for you before anything else does.

The first is a hard constraint. If you are using Spring Boot version 1.5.x you MUST use Spring Cloud Zuul RateLimit version 1.7.x, and the instruction is to look at Maven Central and pick the latest artifact in that version line. So the library has a compatibility floor at Spring Boot 1.5 for the 1.7.x line.

The second note is the dependency snippet, and the version is a placeholder you have to fill in:

```xml
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>${latest-version}</version>
</dependency>
```

The group id is com.marcosbarbero.cloud rather than an org.springframework group, which tells you the module is not part of Spring Cloud itself, and the artifact is published to Maven Central with a badge in the README header.

The version history explains the shape of the project. The 1.7.x line runs through 1.7.6.RELEASE from 2020-09-06, and the 2.4.x line through v2.4.3.RELEASE from 2021-10-26. So the last tagged release is from 2021, while the last push to the repository is from 2026-10-16. The project is being worked on, but the released artefacts are almost five years old, and the topics list still carries hacktoberfest2021 alongside hacktoberfest, which dates the community activity.

The repository layout is a Maven multi-module project, with a pom.xml at the root, a mvnw wrapper and a.mvn directory, and the modules named for what they do. spring-cloud-zuul-ratelimit-core is the implementation, spring-cloud-starter-zuul-ratelimit is the starter that pulls in the dependencies, spring-cloud-zuul-ratelimit-dependencies is the dependency management, spring-cloud-zuul-ratelimit-coverage is the coverage reporting module and spring-cloud-zuul-ratelimit-tests is the test suite. There is a README.adoc as the main documentation, an assets/ directory for the images the asciidoc references, a docs/ directory, a SECURITY.md, a CONTRIBUTING.adoc and a CODE_OF_CONDUCT.md, plus a.ci/ directory and a.travis.yml.

The build uses the Maven wrapper rather than a system Maven, which is the detail that will save you the most time if your local Maven version differs from the one the project expects.

## Conclusion

spring-cloud-zuul-ratelimit suits a team running Spring Cloud Netflix Zuul as a gateway that needs per-service limits without writing the filter and the counter storage themselves, and the per-service policy list is the part that makes it more than a global throttle. It is the wrong choice if you have moved off Netflix Zuul, since this is tied to that gateway specifically, and it is the wrong choice for a greenfield gateway. Before adopting it, check the Spring Boot version you run against the version matrix, because the README states that Spring Boot 1.5.x users must use the 1.7.x line, and note that the newest release tag is v2.4.3.RELEASE from 2021-10-26 while the repository itself is being worked on.

## FAQ

### What does spring-cloud-zuul-ratelimit do?

It is a module to enable rate limit per service in Netflix Zuul, adding a filter, a pluggable counter storage and a policy list that can be applied globally or per downstream service. The project is licensed Apache-2.0 and the artifact is published to Maven Central as com.marcosbarbero.cloud:spring-cloud-zuul-ratelimit.

### Which storage backends can I use for the rate limit counters?

Redis, Consul, Spring Data JPA, and the Bucket4j family, which is bucket4j-core plus bucket4j-jcache with cache-api, or one of the bucket4j-hazelcast, bucket4j-infinispan and bucket4j-ignite variants that depend on the JCache one. The JPA option needs a rate table with rate_key, remaining, remaining_quota, reset and expiration columns.

### How do I choose what a rate limit applies to?

The type list under a policy selects the key strategy, and the choices are user, origin, url, url_pattern, role, http_method and http_header, or no type at all for a global per-service limit. The README states that Authenticated User, Request Origin, URL, ROLE and Request Method can be combined by adding multiple values to the list.

### What is the difference between limit and quota in a Zuul rate limit policy?

The limit is the request number limit per refresh interval window, and the quota is the request time limit per refresh interval window in seconds, with refresh-interval defaulting to 60 seconds. A policy can therefore cap both how many requests arrive and how long those requests take in total.

### Which Spring Boot version does the current rate limit module support?

The README states that if you are using Spring Boot version 1.5.x you MUST use Spring Cloud Zuul RateLimit version 1.7.x, and points at Maven Central for the latest artifact in that line. The newer tags are in the 2.4.x line, with v2.4.3.RELEASE from 2021-10-26.

## Sources

- [License: Apache-2.0](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit/blob/master/LICENSE)
- [marcosbarbero/spring-cloud-zuul-ratelimit on GitHub](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit)
- [Project website](https://blog.marcosbarbero.com/spring-cloud-netflix-zuul-rate-limit/)
- [README](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit/blob/master/README.md)
- [Releases](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/marcosbarbero-spring-cloud-zuul-ratelimit
