As AI coding assistants become deeply integrated into our daily workflows, engineering teams are running into a frustrating wall: context bloat. Modern enterprise applications span multiple repositories, microservices, and databases. When you ask an AI a complex architectural question, brute-force ingestion often leads to massive token bills and hallucinated answers.
Better context reduces token consumption and delivers more precise results.
To prove this, I ran a head-to-head benchmark using a well-known, highly complex open-source project: Spring PetClinic Microservices. This repository separates logic across an API Gateway, Config Server, Discovery Server, and multiple backend services. It is the perfect stress test for an AI's ability to navigate cross-module logic without getting lost in the noise.
Tools:
Ether (Model: Kimi k3) You can download Ether for free at https://www.overclock.it.com/features
Cursor (Free Plan, Model: Auto)
The Benchmark Prompt
"Trace the exact cross-service data flow when a user requests an owner's details via the API Gateway (
GET /api/gateway/owners/{ownerId}). Identify the specific class and method in theapi-gatewaymodule that aggregates this data. Then, list the exact REST clients (WebClient or Feign) it uses to call downstream services, the specific controller classes and methods triggered in thecustomers-serviceandvisits-service, and the underlying database tables queried in both backend microservices to assemble the final response. Do not generate any code; just provide a step-by-step architectural trace with the exact file names."
Tool 1: Ether (Kimi k3)
The Analysis
Ether utilized its context-pruning orchestration to filter out irrelevant files before querying the LLM. It traced the entire stack, identifying layers that Cursor completely missed.
Token Consumption
Ether Output:
Cross-Service Data Flow Trace: GET /api/gateway/owners/{ownerId}
1. Entry Point (Browser / Frontend)
The AngularJS UI issues the request from:
-
spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.controller.js→$http.get('api/gateway/owners/' + $stateParams.ownerId)
This hits the API Gateway, which is the single ingress point for the Angular app (routes configured in spring-petclinic-api-gateway/src/main/resources/application.yml).
2. API Gateway — Aggregation Layer
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java
-
Class:
ApiGatewayController(annotated@RestController+@RequestMapping("/api/gateway")) -
Method:
getOwnerDetails(int ownerId)mapped via@GetMapping(value = "owners/{ownerId}") -
Return type:
Mono<OwnerDetails>(fully reactive, non-blocking WebFlux)
The aggregation logic is:
- Call
customersServiceClient.getOwner(ownerId)→ returnsMono<OwnerDetails>containing the owner and their pets (but no visits yet). -
flatMapon the owner → extract pet IDs viaowner.getPetIds(). - Call
visitsServiceClient.getVisitsForPets(petIds)→ returnsMono<Visits>. - Wrap the visits call in a Resilience4j circuit breaker (
cbFactory.create("getOwnerDetails")), with fallbackemptyVisitsForPets()(returns empty visits list on failure — graceful degradation). -
.map(addVisitsToOwner(owner))merges visits into each pet (matching onpetId) and returns the enrichedOwnerDetails.
3. Downstream REST Clients (both use WebClient, not Feign)
A. Customers client
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java
-
Class:
CustomersServiceClient -
Method:
getOwner(int ownerId) -
HTTP call:
GET http://customers-service/owners/{ownerId}(uses the@LoadBalancedWebClient.Builderdefined inApiGatewayApplication.java, so service discovery resolves thecustomers-servicehostname)
B. Visits client
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java
-
Class:
VisitsServiceClient -
Method:
getVisitsForPets(List<Integer> petIds) -
HTTP call:
GET http://visits-service/pets/visits?petId={comma-separated IDs}(joins IDs viajoinIds())
4. Downstream Controllers Triggered
A. Customers Service
File: spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java
-
Class:
OwnerResource(@RestController, base path/owners) -
Method:
findOwner(int ownerId)→@GetMapping("/{ownerId}") - Delegates to
OwnerRepository.findById(ownerId)(Spring Data JPA). - The returned
Ownerentity eagerly/fetch-joins itspetscollection (and each pet'sPetType), which is what populates the pet IDs/names/birthDates/types in the gateway'sOwnerDetails.
B. Visits Service
File: spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
-
Class:
VisitResource(@RestController) -
Method:
read(@RequestParam("petId") List<Integer> petIds)→@GetMapping("pets/visits") - Delegates to
VisitRepository.findByPetIdIn(petIds)(spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java) - Wraps the result in its inner record
Visits(List<Visit> items)and returns it.
5. Underlying Database Tables Queried
Customers Service DB
Schema files:
spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sqlspring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql
Tables touched when OwnerRepository.findById() fetches the owner graph:
| Table | Role |
|---|---|
owners |
Owner row (id, first_name, last_name, address, city, telephone) |
pets |
Pets belonging to the owner (FK owner_id → owners.id) |
types |
Pet type lookup for each pet (FK pets.type_id → types.id) |
Visits Service DB
Schema files:
spring-petclinic-visits-service/src/main/resources/db/hsqldb/schema.sqlspring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql
| Table | Role |
|---|---|
visits |
Rows filtered via WHERE pet_id IN (...) by findByPetIdIn() — columns id, pet_id, visit_date, description
|
End-to-End Sequence Summary
Browser (Angular owner-details.controller.js)
│ GET /api/gateway/owners/{ownerId}
▼
ApiGatewayController.getOwnerDetails() [api-gateway]
├─► CustomersServiceClient.getOwner(ownerId) (WebClient)
│ GET http://customers-service/owners/{ownerId}
│ ▼
│ OwnerResource.findOwner(ownerId) [customers-service]
│ ▼ OwnerRepository.findById()
│ Tables: owners, pets, types ◄── returns OwnerDetails(+pets)
│
├─► extract owner.getPetIds()
│
└─► VisitsServiceClient.getVisitsForPets(petIds) (WebClient + Resilience4j CB "getOwnerDetails")
GET http://visits-service/pets/visits?petId=1,2,...
▼
VisitResource.read(petIds) [visits-service]
▼ VisitRepository.findByPetIdIn()
Table: visits ◄── returns Visits(items)
▼
addVisitsToOwner() merges visits into each pet (filter by petId)
▼
Mono<OwnerDetails> serialized → JSON response to browser
Key architectural notes:
- The gateway performs client-side service composition (API composition pattern) — the two downstream calls are sequential in code (
flatMap), because the visits call depends on pet IDs from the owner response. - Resilience is handled by a Resilience4j
ReactiveCircuitBreakernamed"getOwnerDetails"with a 10-secondTimeLimiterConfig(default inApiGatewayApplication.java); on visits-service failure the owner data is still returned with empty visit lists. - Both clients use
@LoadBalanced WebClient.Builder(defined inspring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java) with Eureka service discovery — there is no Feign in this path.
Tool 2: Cursor (Auto)
The Analysis
Cursor provided a solid, accurate trace of the backend Java services. It explored 17 files and made 13 searches. It correctly identified that the project uses WebClient rather than Feign, and it mapped the downstream JPA repositories.
Token Consumption
Cursor Output:
Here is the exact cross-service trace for GET /api/gateway/owners/{ownerId}.
Overview
This is a BFF-style aggregation in spring-petclinic-api-gateway. The gateway does not use Feign anywhere in this project. Both downstream calls use WebClient (via a @LoadBalanced WebClient.Builder bean in ApiGatewayApplication.java).
Step 1 — API Gateway entry point
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java
This method orchestrates the full response:
- Calls
customersServiceClient.getOwner(ownerId)to fetch owner + pets. - On success, calls
visitsServiceClient.getVisitsForPets(owner.getPetIds()). - Wraps the visits call in a Resilience4j reactive circuit breaker named
"getOwnerDetails"(falls back toemptyVisitsForPets()on failure). - Merges visits into each pet via the private helper
addVisitsToOwner(OwnerDetails owner). - Returns
Mono<OwnerDetails>.
Pet IDs are extracted in OwnerDetails.getPetIds() (spring-petclinic-api-gateway/.../dto/OwnerDetails.java) from the pets list returned by customers-service.
Step 2 — Downstream REST clients (WebClient only)
2a. Customers client
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java
-
Class:
CustomersServiceClient -
Method:
getOwner(int ownerId) -
Target:
GET http://customers-service/owners/{ownerId}
2b. Visits client
File: spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java
-
Class:
VisitsServiceClient -
Method:
getVisitsForPets(List<Integer> petIds) -
Target:
GET http://visits-service/pets/visits?petId={id1,id2,...}
Service names (customers-service, visits-service) are resolved through Spring Cloud LoadBalancer because the WebClient.Builder is annotated @LoadBalanced in ApiGatewayApplication.java.
Step 3 — customers-service
File: spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java
-
Method:
findOwner(int ownerId) -
Effective endpoint:
GET /owners/{ownerId} -
Persistence layer:
OwnerRepository.findById(ownerId)
Because Owner has @OneToMany(fetch = FetchType.EAGER) on pets, and each Pet has @ManyToOne on PetType, a single owner lookup triggers reads from three tables:
-
owners: Primary lookup viafindById -
pets: Eager@OneToMany(mappedBy = "owner") -
types:@ManyToOne @JoinColumn(name = "type_id")on eachPet
The JSON response includes owner fields and pets (with type), but no visits — those are added later by the gateway.
Step 4 — visits-service
File: spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
-
Method:
read(List<Integer> petIds) -
Effective endpoint:
GET /pets/visits?petId=1,2,3 -
Persistence layer:
VisitRepository.findByPetIdIn(Collection<Integer> petIds)
Database table queried:
-
visits(SELECT ... FROM visits WHERE pet_id IN (...)) - Schema definition:
spring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql
Step 5 — Gateway aggregation and final response
Back in ApiGatewayController.addVisitsToOwner():
- Iterates each
PetDetailsin the owner. - Filters
Visits.items()wherevisit.petId() == pet.id(). - Appends matching visits into that pet's
visitslist. - Returns the enriched
OwnerDetails.
Cursor End-to-End Sequence Summary
ApiGatewayController.getOwnerDetails() [api-gateway]
├─► CustomersServiceClient.getOwner(ownerId) (WebClient)
│ GET http://customers-service/owners/{ownerId}
│ ▼
│ OwnerResource.findOwner(ownerId) [customers-service]
│ ▼ OwnerRepository.findById()
│ Tables: owners, pets, types ◄── returns OwnerDetails(pets, no visits)
│
├─► extract owner.getPetIds()
│
└─► VisitsServiceClient.getVisitsForPets(petIds) (WebClient)
GET http://visits-service/pets/visits?petId=1,2,...
▼
VisitResource.read(petIds) [visits-service]
▼ VisitRepository.findByPetIdIn()
Table: visits ◄── returns Visits JSON
▼
addVisitsToOwner() merge
▼
OwnerDetails (owner + pets + visits)
The Final Verdict
| Metric / Aspect | Cursor (Auto) | Ether (Kimi k3) | Winner |
|---|---|---|---|
| Gross Context Usage | ~31.8K Tokens | 26.1K Tokens | Ether (18% cost efficiency) |
| Active / Billed Tokens | ~31.8K Tokens | 18.4K Tokens (7.7K cached) | Ether (42% cost efficiency) |
| Tracing Depth | Started at API Gateway | Traced back to AngularJS UI | Ether |
| Service Discovery | Omitted | Identified Eureka Resolution | Ether |
| Database Schema | Inferred via JPA | Located physical .sql files |
Ether |


Top comments (0)