Developer
News and Updates
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Aug 31, 2026

Rate-limit REST endpoints with annotations

Audience: Data Center app and plugin developers, including Marketplace vendors who expose REST endpoints and want to protect them from abusive or runaway traffic.

Available from: the atlassian-ratelimiting 5.0.0 rate-limiting platform plugin, bundled with the following product releases:

ProductVersion
Confluence Data Center11.0
Jira Software Data Center12.0
Jira Service Management Data Center12.0
Bitbucket Data Center11.0
Bamboo Data Center13.0
Crowd Data Center8.0

What is this feature?

The annotation-based, API-specific rate limiter lets you cap the request rate of an individual REST endpoint by adding a single Java annotation to a JAX-RS resource class or method. No filters, no wiring, no product-specific code.

PropertyBehaviour
Scope of a limitPer node and per user. Each user gets their own budget on each cluster node.
User identityAuthenticated user; OAuth2 2LO (client credentials) users are limited per client_id; everyone else is limited as a single shared anonymous bucket.
UnitPermits per second (a token bucket with a burst capacity of roughly one second of traffic).
When exceededThe request is rejected with HTTP 429 before it reaches your resource method.
Runtime controlAdministrators can override each limit with a system property — no code change or rebuild required. For a constant endpoint limit, a value of 0 or lower disables the limit.

This is not the same as the admin-configured global rate limiting feature (Settings → Rate limiting). That one limits a user's total traffic across the product. This feature protects one specific endpoint. When both a global rate limit and an endpoint-specific rate limit apply, the effective limit is the lower of the two. Endpoint-specific rate limiting is therefore most useful for expensive or abuse-prone APIs, rather than as a replacement for the product-wide global rate limit.

Step 1 — Add the dependency

Add the public API artifact with provided scope. It contains only annotations and one interface; the runtime implementation ships with the product.

1
2
3
4
5
6
7
<dependency>
    <groupId>com.atlassian.ratelimiting</groupId>
    <artifactId>rate-limiting-public-api</artifactId>
    <version>5.0.0</version>
    <scope>provided</scope>
</dependency>

Step 2 — Annotate the endpoint

Put @RateLimited on a JAX-RS resource method (limits that one operation) or on the resource class (applies to every method that does not have its own annotation).

1
2
3
4
5
6
7
8
9
10
11
12
import com.atlassian.ratelimiting.annotation.RateLimited;

@Path("/questions")
public class QuestionsResource {

    @GET
    @RateLimited(permitsPerSecond = 15)
    public Response getQuestions() {
        // ...
    }
}

The platform discovers the annotation when REST resources start up and installs the enforcement filter for you.

@RateLimited attributes

AttributeTypeDefaultMeaning
permitsPerSeconddouble0Allowed requests per second, per user, per node. Fractional values are valid (0.5 = one request every 2 seconds). Any value ≤ 0 means "no limit".
typeRateLimited.TypeALLALL — limit every user. TWO_LO — limit only OAuth2 2LO (client credentials) requests, keyed by client id; interactive users are untouched.
propertyNameStringderivedA stable, human-friendly key for the system property that overrides this limit.

Precedence

  • A method-level annotation always wins over a class-level annotation on the same resource.
  • A system property overrides the value compiled into the annotation.

Step 3 — Choose your override property name

If you do not set propertyName, the override key is derived from the code and is therefore brittle — it changes if you rename the class or the method:

1
2
com.atlassian.ratelimiting.api.<lowercased.fully.qualified.classname>.<lowercased methodname>

Whatever you put in propertyName becomes the property name verbatim — the platform does not prepend com.atlassian.ratelimiting.api. to it. Always include the prefix yourself, as in the example below, so your limit stays inside the canonical namespace.

Set an explicit propertyName so administrators get a stable, documentable knob:

1
2
3
4
5
@GET
@RateLimited(permitsPerSecond = 15,
             propertyName = "com.atlassian.ratelimiting.api.myapp.questions.list.rps")
public Response getQuestions() { /* ... */ }

Advanced — dynamic limits with @RateLimitRule

Use this only when a single constant is not enough — for example when the limit should depend on query parameters, headers, or the authentication type of the request.

Implement Rule. The class must have a public no-arg constructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.atlassian.ratelimiting.annotation.RateLimited;
import com.atlassian.ratelimiting.annotation.Rule;
import jakarta.ws.rs.container.ContainerRequestContext;

public class ExpensiveExpandRule implements Rule {

    // Group name, and the suffix of this rule's system-property override. Keep it stable.
    @Override
    public String name() {
        return "expensive_expand";
    }

    // Should this rule be considered for this request?
    @Override
    public boolean appliesTo(RateLimited.Type requestType, ContainerRequestContext ctx) {
        return requestType == RateLimited.Type.TWO_LO
                && ctx.getUriInfo().getQueryParameters().containsKey("expand");
    }

    // The limit to apply when this rule applies.
    @Override
    public double rateLimit(ContainerRequestContext ctx) {
        return 5;
    }
}

Reference the rule classes from @RateLimitRule. A @RateLimited annotation must also be present — it supplies the base property name.

1
2
3
4
5
6
7
import com.atlassian.ratelimiting.annotation.RateLimitRule;

@GET
@RateLimited(propertyName = "com.atlassian.ratelimiting.api.myapp.spaces.rps")
@RateLimitRule(rules = { ExpensiveExpandRule.class, GenericTwoLoRule.class })
public PageResponse<Space> spaces(...) { /* ... */ }

How the effective limit is resolved

  1. Rule classes are grouped by Rule#name(), which defaults to the simple class name.
  2. Within a group, the rules that apply to the request are AND-combined — the lowest rate wins.
  3. Across all groups, the lowest rate wins. That is the effective limit.
  4. A resolved value of 0 or less means "no limit" for that request.

Setting a rule-group property to 0 does not disable that group. A value of 0 or less makes the platform ignore the property and evaluate the rules instead. To effectively disable a group, set a very high permits-per-second value.

Gotchas

  • Only one @RateLimitRule per class or method. List all rule classes inside its rules() array.
  • When @RateLimitRule is present it fully replaces the permitsPerSecond value from @RateLimited — there is no fallback to it.
  • @RateLimitRule without a companion @RateLimited is ignored.
  • Keep appliesTo() and rateLimit() cheap — they run on every request to that endpoint.

What the user sees when limited

1
2
3
4
5
HTTP/1.1 429 Too Many Requests
Retry-After: 1

{"rateLimited": true}

Retry-After is in seconds and is derived from the effective limit: 1 second for rates of 1/s or higher, otherwise the time needed to earn one permit.

Runtime configuration (for administrators)

All of the following are JVM system properties.

PropertyPurposeDefault
com.atlassian.ratelimiting.api.<lowercased-fqcn>.<lowercased-method>Override the permits-per-second of one endpoint, using the key derived from the resource class and method. This is checked first. On an endpoint without @RateLimitRule, 0 or a negative value switches the limit off.annotation value
<propertyName>When @RateLimited sets propertyName, that string is the property name exactly as written — the com.atlassian.ratelimiting.api. prefix is not prepended. Include it yourself.annotation value
<base>.typeOverride the request type (ALL or TWO_LO). <base> is the derived key or your propertyName.annotation value
<base>.<ruleName>Override the limit of one rule group on a rules-based endpoint. <base> is the derived key or your propertyName.rule value
com.atlassian.ratelimiting.api.rest.enforce.allWhen true, apply a default limit to annotated endpoints that do not declare one.false
com.atlassian.ratelimiting.api.rest.rps.defaultThe default permits-per-second used when rest.enforce.all is true.5.0
com.atlassian.ratelimiting.api.rest.request.type.defaultDefault request type when none is specified.ALL
com.atlassian.ratelimiting.api.cache.expiry.minutesIdle expiry of a user's token bucket.20

Recommendations

  • Rate limit the endpoints that are expensive or abuse-prone not everything.
  • Always set an explicit propertyName so the limit survives refactoring and can be documented for administrators.
  • Remember the limit is per node: on an N-node cluster a user can achieve up to N × the configured rate if their requests spread across nodes. Size the value accordingly.
  • Prefer type = TWO_LO when the risk comes from integrations rather than from people, so you do not degrade the interactive UI.
  • Start permissive, observe, then tighten. Announce the limit in your API documentation before enforcing it.
  • Make sure your own clients handle 429 and back off using Retry-After.

Rate this page: