SpEL Expression Reference for Cycles
The @Cycles annotation uses Spring Expression Language (SpEL) to evaluate estimate and actual cost values dynamically. This page is a comprehensive reference for writing SpEL expressions in Cycles.
Where expressions are used
The @Cycles annotation accepts SpEL expressions in two places:
| Attribute | Evaluated when | Purpose |
|---|---|---|
value / estimate | Before the method runs | Determines the reservation amount |
actual | After the method returns | Determines the commit amount |
Available variables
Method parameters
Parameters are available by index and by name:
@Cycles("#p0 * 10")
public String generate(int tokens) { ... }| Variable | Meaning |
|---|---|
#p0, #p1, #p2, ... | Parameters by index (zero-based) |
#paramName | Parameters by name (requires -parameters compiler flag) |
Parameter names
To use parameter names instead of indexes, compile with the -parameters flag:
<!-- Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>// Gradle
tasks.withType(JavaCompile) {
options.compilerArgs << '-parameters'
}With this flag:
@Cycles("#tokens * 10")
public String generate(int tokens) { ... }Without it, use #p0:
@Cycles("#p0 * 10")
public String generate(int tokens) { ... }Return value
The #result variable is available only in the actual expression, evaluated after the method returns:
@Cycles(estimate = "5000", actual = "#result.usage.totalTokens * 8")
public ChatResponse chat(String prompt) { ... }If the method returns null, #result is null. Accessing properties on it will throw a NullPointerException.
Other variables
| Variable | Meaning |
|---|---|
#args | All method arguments as an Object[] array |
#target | The target object instance (the bean the method belongs to) |
Expression examples
Fixed values
@Cycles("500")
public String summarize(String text) { ... }A literal number is the simplest expression. It evaluates to that value every time.
Arithmetic on parameters
@Cycles("#p0 * 10")
public String generate(int maxTokens) { ... }@Cycles("#p0.length() / 4 * 8")
public String processText(String input) { ... }Using named parameters
@Cycles("#maxTokens * 10")
public String generate(int maxTokens) { ... }Using the return value
@Cycles(estimate = "5000", actual = "#result.length() * 5")
public String translate(String text) { ... }@Cycles(estimate = "#p1 * 10",
actual = "#result.usage.totalTokens * 8")
public ChatResponse chat(String prompt, int estimatedTokens) { ... }Accessing nested properties
@Cycles(estimate = "#request.estimatedTokens * 10",
actual = "#result.metadata.totalCost")
public Response process(Request request) { ... }Conditional expressions
@Cycles("#p0.length() > 1000 ? 10000 : 2000")
public String summarize(String text) { ... }Math functions
@Cycles("T(Math).max(#p0 * 10, 1000)")
public String generate(int tokens) { ... }@Cycles("T(Math).min(#p0.length() / 4 * 8, 50000)")
public String process(String input) { ... }Accessing the args array
@Cycles("#args[0].length() * #args[1]")
public String process(String text, int costPerChar) { ... }Accessing the target bean
@Cycles("#target.getEstimateMultiplier() * #p0")
public String process(int tokens) {
// ...
}
public int getEstimateMultiplier() {
return 10;
}Evaluation rules
Return type
The expression must evaluate to a Number. The result is converted to a long via Number.longValue().
Non-negative
The evaluated value must be >= 0. A negative value throws IllegalArgumentException.
Null safety
If the expression evaluates to null, an IllegalArgumentException is thrown. Guard against null:
// Safe: use a fallback
@Cycles(actual = "#result != null ? #result.cost : 0")
public Result process(String input) { ... }Estimate vs actual
| Attribute | #result available? | When evaluated |
|---|---|---|
value / estimate | No | Before method execution |
actual | Yes | After method returns |
If actual is not specified and useEstimateIfActualNotProvided is true (the default), the estimate value is used as the actual at commit time.
Common patterns
Token-based estimation
@Cycles(estimate = "#prompt.length() / 4 * 10",
actual = "#result.usage.totalTokens * 10",
unit = "USD_MICROCENTS")
public ChatResponse complete(String prompt) { ... }Fixed estimate with actual from response
@Cycles(estimate = "10000",
actual = "#result.cost",
unit = "USD_MICROCENTS")
public ApiResponse callExternalApi(Request request) { ... }Multiple parameters
@Cycles("#p0 * #p1 * 8")
public String batchProcess(int documents, int tokensPerDoc) { ... }Using enum or constant values via SpEL
@Cycles("T(com.example.CostTable).estimateFor(#p0)")
public String process(String modelName) { ... }Troubleshooting
"Expression evaluated to null"
The expression returned null. Common cause: accessing a property on a null object. Add a null check:
actual = "#result?.cost != null ? #result.cost : 0""Charge amount must not be negative"
The expression evaluated to a negative number. Ensure your math cannot produce negative values:
estimate = "T(Math).max(#p0 * 10, 0)""Parameter name not found"
You used #paramName but did not compile with -parameters. Use #p0 index-based access or add the compiler flag.
Expression parse errors
Check for typos in method names, property paths, or operator usage. SpEL follows Java-like syntax but uses # for variables.
Next steps
- Getting Started with the Spring Boot Starter — annotation usage
- Client Configuration Reference — all configuration properties
- Error Handling Patterns — handling evaluation failures
