Repository navigation
Java: outbound HTTP calls through an injected client, a format-string URL or a request builder yield no HTTP_CALLS #2668
Description
Version
codebase-memory-mcp 0.12.0
Platform
Linux (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
ui
What happened, and what did you expect?
Indexing a Java client that calls a Java server produces HTTP_CALLS edges only when the HTTP client is
constructed inline at the call site (new RestTemplate().getForObject("/v1/orders", …)) and the URL is a
literal or an expr + "literal" concatenation. Three common production shapes produce no HTTP_CALLS edge:
- the client is an injected field, parameter or local whose type comes from a jar
(private final RestTemplate restTemplate;), or an in-house wrapper around such a client; - the URL is a formatted string (
"%s/challenges/%s/verified/%s".formatted(base, id, token)); - the URL goes into a request builder and the request is dispatched by a later call
(like for OkHttpnew Request.Builder().url("/v1/orders")…client.newCall(request).execute())
Expected: see the table under "Result vs expected" in the Reproduction section.
Nearest existing issues, none covering Java: #706 , #1147, #1009.
Reproduction
1. Code. Two dummy repos
Client repo /tmp/cbm-java-http-client, all files under src/main/java/com/acme/client/:
// InlineClient.java (control: these two DO produce HTTP_CALLS)
package com.acme.client;
import org.springframework.web.client.RestTemplate;
public class InlineClient {
public String literal() {
return new RestTemplate().getForObject("/v1/orders", String.class);
}
public String concat(String baseUrl) {
return new RestTemplate().getForObject(baseUrl + "/challenges/state", String.class);
}
}// JarFieldClient.java (gap 1: jar-typed field; `verified` also hits gap 2)
package com.acme.client;
import org.springframework.web.client.RestTemplate;
public class JarFieldClient {
private final RestTemplate restTemplate;
public JarFieldClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; }
public String orders() {
return restTemplate.getForObject("/v1/orders", String.class);
}
public String verified(String baseUrl, String challengeId, String token) {
return restTemplate.getForObject("%s/challenges/%s/verified/%s".formatted(baseUrl, challengeId, token), String.class);
}
}// LocalVarClient.java (gap 1: jar-typed parameter and local)
package com.acme.client;
import org.springframework.web.client.RestTemplate;
public class LocalVarClient {
public String orders(RestTemplate rt) {
return rt.getForObject("/v1/orders", String.class);
}
public String byId(RestTemplate rt) {
var t = rt;
return t.getForObject("/v1/orders/{id}", String.class, "42");
}
}// WrapperClient.java (gap 1: in-house wrapper type from a jar; build() returns a RestTemplate)
package com.acme.client;
import com.acme.http.AcmeHttpClientBuilder;
public class WrapperClient {
private final AcmeHttpClientBuilder builder;
public WrapperClient(AcmeHttpClientBuilder builder) { this.builder = builder; }
public String orders() {
return builder.build().getForObject("/v1/orders", String.class);
}
}// FormatInlineClient.java (gap 2 alone: inline client, format-string URL)
package com.acme.client;
import org.springframework.web.client.RestTemplate;
public class FormatInlineClient {
public String verified(String baseUrl, String challengeId, String token) {
return new RestTemplate().getForObject("%s/challenges/%s/verified/%s".formatted(baseUrl, challengeId, token), String.class);
}
}// OkClient.java (gap 3 alone: inline client, URL into a builder, dispatched by a later call)
package com.acme.client;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkClient {
public Response orders() throws Exception {
Request request = new Request.Builder().url("/v1/orders").get().build();
return new OkHttpClient().newCall(request).execute();
}
}Server repo /tmp/cbm-java-server, under src/main/java/com/acme/server/ (JAX-RS; indexes correctly):
// OrderResource.java
package com.acme.server;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@Path("/v1/orders")
public class OrderResource {
@GET public String list() { return "[]"; }
@GET @Path("/{id}") public String get(@PathParam("id") String id) { return id; }
@POST public String create(String body) { return body; }
}// ChallengeResource.java
package com.acme.server;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@Path("/challenges")
public class ChallengeResource {
@GET @Path("/state") public String state() { return "ok"; }
@GET @Path("/{id}/verified/{token}")
public String verified(@PathParam("id") String id, @PathParam("token") String token) { return id; }
}2. Commands. The project ids below are what the indexer derives from the /tmp/... paths.
codebase-memory-mcp cli index_repository '{"repo_path":"/tmp/cbm-java-http-client","mode":"moderate"}'
codebase-memory-mcp cli index_repository '{"repo_path":"/tmp/cbm-java-server","mode":"moderate"}'
codebase-memory-mcp cli query_graph '{"project":"tmp-cbm-java-http-client","query":"MATCH (f)-[e:HTTP_CALLS]->(r:Route) RETURN f.file_path, f.name, r.qualified_name"}'
codebase-memory-mcp cli query_graph '{"project":"tmp-cbm-java-http-client","query":"MATCH (f)-[e:CALLS]->(g) RETURN f.file_path, f.name, g.qualified_name"}'
codebase-memory-mcp cli query_graph '{"project":"tmp-cbm-java-server","query":"MATCH (r:Route) RETURN r.qualified_name"}'
codebase-memory-mcp cli index_repository '{"repo_path":"/tmp/cbm-java-http-client","mode":"cross-repo-intelligence","target_projects":["tmp-cbm-java-server"]}'
3. Result vs expected.
Client HTTP_CALLS (CLI output; expected 9 rows, one per call site in the table below):
rows: 2 (cols: f.file_path f.name r.qualified_name)
src/main/java/com/acme/client/InlineClient.java literal __route__GET__/v1/orders
src/main/java/com/acme/client/InlineClient.java concat __route__GET__/challenges/state
returned: 2
Server Route nodes (correct):
rows: 5 (cols: r.qualified_name)
__route__GET__/v1/orders
__route__GET__/v1/orders/{}
__route__POST__/v1/orders
__route__GET__/challenges/state
__route__GET__/challenges/{}/verified/{}
returned: 5
cross-repo-intelligence: "cross_http_calls":2,"total_cross_edges":2 (the two InlineClient edges).
Expected 9 (all rows below should be a HTTP_CALLS).
| call site | shape | expected Route | current HTTP_CALLS |
|---|---|---|---|
InlineClient.literal |
new RestTemplate().getForObject("/v1/orders", …) |
__route__GET__/v1/orders |
yes |
InlineClient.concat |
new RestTemplate().getForObject(baseUrl + "/challenges/state", …) |
__route__GET__/challenges/state |
yes |
JarFieldClient.orders |
restTemplate.getForObject("/v1/orders", …), RestTemplate field typed from a jar |
__route__GET__/v1/orders |
no |
LocalVarClient.orders |
rt.getForObject("/v1/orders", …), RestTemplate rt parameter |
__route__GET__/v1/orders |
no |
LocalVarClient.byId |
var t = rt; t.getForObject("/v1/orders/{id}", …) |
__route__GET__/v1/orders/{} |
no |
WrapperClient.orders |
builder.build().getForObject("/v1/orders", …), wrapper type from a jar |
__route__GET__/v1/orders |
no |
FormatInlineClient.verified |
new RestTemplate().getForObject("%s/challenges/%s/verified/%s".formatted(…), …) |
__route__GET__/challenges/{}/verified/{} |
no |
JarFieldClient.verified |
same format string on the jar-typed field (gaps 1 and 2) | __route__GET__/challenges/{}/verified/{} |
no |
OkClient.orders |
new Request.Builder().url("/v1/orders").get().build() then new OkHttpClient().newCall(request).execute() |
__route__GET__/v1/orders |
no |
Logs
Diagnostics trajectory (memory / performance / leak issues)
Project scale (if relevant)
No response
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
DeusData commentedon Oct 19, 2026
Thank you for the shareable client/server fixtures and the comparison table. Separating injected receivers, formatted URLs, and builder-based requests makes the missing HTTP_CALLS reports much easier to evaluate independently. There is no verified fix to report yet.