GitHub Copilot CLI offers two fundamentally different approaches for navigating and understanding Java code: the default approach using grep, glob, and shell tools (text-based pattern matching), and the LSP approach using Eclipse JDT Language Server (semantic code intelligence). After analyzing the Copilot agent runtime source code, system prompts, tool implementations, and the Java LSP plugin architecture, the conclusion is clear: the LSP approach delivers significantly better results for Java projects, at the cost of higher initial setup complexity and memory usage. LSP produces more accurate code navigation with fewer tool calls (and therefore fewer tokens and premium requests), while the grep/glob approach requires the LLM to perform multiple iterative searches and read more files to achieve the same understanding. For any serious Java development work, the LSP approach is the recommended choice.
The default approach relies on three core tools, all built on ripgrep for performance12:
| Tool | Engine | Purpose | Token Cost per Call |
|---|---|---|---|
| glob | ripgrep --files |
Find files by name pattern | Low (file paths only) |
| grep | ripgrep | Search file contents by regex | Medium-High (matching lines + context) |
| bash | Shell | Fallback for anything else | Variable |
How it works in practice for Java:
- The LLM uses
globto find files:glob("**/UserService.java") - It uses
grepto search for symbols:grep("class UserService", type: "java") - It reads matching files with
viewto understand code - It iterates — searching for related classes, interfaces, imports
- For cross-references, it performs additional grep calls:
grep("UserService", type: "java")
Key characteristic: The LLM must infer code relationships from text patterns. It has no understanding of Java's type system, inheritance hierarchy, or method resolution.
The LSP approach adds a semantic code intelligence layer via the Eclipse JDT Language Server3. When configured, Copilot CLI launches a persistent Java language server process that understands the full compilation model4.
9 LSP operations available5:
| Operation | Input | Output | Token Cost |
|---|---|---|---|
goToDefinition |
file + position | Exact definition location | Very Low |
findReferences |
file + position | All usage locations | Low |
goToImplementation |
file + position | Implementation locations | Very Low |
hover |
file + position | Javadoc + type signature | Low-Medium |
documentSymbol |
file | Class/method/field outline | Low |
workspaceSymbol |
query string | Matching symbols across project | Low |
incomingCalls |
file + position | What calls this function | Low |
outgoingCalls |
file + position | What this function calls | Low |
rename |
file + position + newName | All files changed | Very Low (metadata) |
How it works in practice for Java:
- The LLM uses
workspaceSymbol("UserService")→ gets exact location - It uses
documentSymbolon the file → gets full class structure (methods, fields, constructors) - It uses
goToDefinitionto navigate to specific types → instant navigation - It uses
findReferencesto find all usages → complete, accurate list - It uses
incomingCalls/outgoingCallsfor call graph analysis
Key characteristic: The LSP server performs actual Java compilation. It understands generics, inheritance, method overloading, annotations, and the full Maven/Gradle dependency tree.
The system prompt explicitly defines a preference order for code search tools6:
When searching code, the preference order for tools to use is:
1. code intelligence tools (if available)
2. LSP-based tools (if available)
3. glob
4. grep with glob pattern
5. shell tool
This means: when LSP is available, the LLM is instructed to prefer it over grep/glob for code navigation tasks.
The system prompt is dynamically generated at runtime. The function codeSearchInstructions() in src/prompts/shared/tools.ts7 checks whether LSP is available:
const lspTool = tools.find((tool) => tool.name === "lsp");
const hasLsp = !!lspTool && lspLanguages.length > 0;When LSP is available for Java (i.e., Eclipse JDT LS is configured), the system prompt includes additional instructions8:
**lsp** (available for: .java (java)):
Use LSP for precise code intelligence:
- Find where a symbol is defined → goToDefinition
- Find all usages of a symbol → findReferences
- Find what calls a function → incomingCalls
- Search symbols by name → workspaceSymbol
- Get type info and docs → hover
The explore agent (used for sub-agent exploration tasks) also includes LSP in its tool list9, meaning sub-agents benefit from the same semantic navigation.
| Scenario | Grep/Glob Approach | LSP Approach | Winner |
|---|---|---|---|
| Find class definition | Searches for class UserService — may match comments, strings, inner classes |
goToDefinition → exact declaration location |
LSP |
| Find all references | grep("UserService") — matches imports, comments, string literals, documentation, other classes with similar names |
findReferences → only actual code references (type-checked) |
LSP |
| Understand inheritance | Must grep for extends/implements, then recursively search parent classes |
goToImplementation → direct navigation; hover → shows full type hierarchy |
LSP |
| Method overloading | Cannot distinguish between save(User) and save(List<User>) via text |
LSP understands full method signatures with generics | LSP |
| Find callers of method | grep("methodName") — matches any occurrence, not just invocations |
incomingCalls → only actual call sites |
LSP |
| Navigate annotations | Must grep for @Service, @Autowired, etc. — no understanding of annotation processing |
LSP understands Spring annotations, resolves injection points | LSP |
| Cross-module references | Must search across all modules manually | LSP resolves across Maven/Gradle module boundaries | LSP |
| Rename symbol | Must grep, hope for uniqueness, manually edit each file | rename → semantically correct rename across all files |
LSP |
| Find files by pattern | glob("**/*Controller.java") — fast, effective |
LSP has no equivalent (use glob) | Grep |
| Search for string literals | grep("error message text") — direct match |
LSP doesn't search string content | Grep |
| Search log messages | grep("logger.info") — direct match |
LSP doesn't help with log searching | Grep |
Verdict: LSP is dramatically better for code navigation and understanding. Grep/glob is better for text-based search patterns (log messages, configuration values, string literals).
| Metric | Grep/Glob Approach | LSP Approach |
|---|---|---|
| First query latency | Instant (~50ms for ripgrep) | Slow on first use: Eclipse JDT LS needs to index the workspace. For a 2,360-file / 394K-line project like Spring AI, initial indexing can take 30–120 seconds10 |
| Subsequent query latency | Instant (~50ms) | Fast (~100-500ms) — server stays warm in memory |
| Multi-step navigation | Requires 3-8 sequential tool calls to trace a code path | 1-2 LSP calls to get the same information |
| End-to-end task time | Longer due to more LLM round-trips | Shorter once server is warm |
| Server startup | None | JDT LS startup: 5-15 seconds (Java process initialization)11 |
Verdict: Grep is faster for one-off searches. LSP is faster for multi-step code understanding tasks because it eliminates iterative searching. The initial startup cost of JDT LS is amortized over many queries.
This is the most impactful difference for cost-conscious users.
Scenario: "Find all classes that implement the ChatModel interface and understand their constructor dependencies"
Grep/Glob approach (estimated ~6-10 tool calls):
| Step | Tool Call | Tokens In (prompt) | Tokens Out (response) |
|---|---|---|---|
| 1 | grep("implements ChatModel", type: "java") |
~50 | ~500 (file paths + lines) |
| 2 | view file 1 (300 lines) |
~50 | ~3,000 |
| 3 | view file 2 (250 lines) |
~50 | ~2,500 |
| 4 | view file 3 (280 lines) |
~50 | ~2,800 |
| 5 | grep("@Autowired\|@Value", type: "java", path: "file1") |
~80 | ~400 |
| 6 | grep for constructor injection patterns |
~80 | ~600 |
| 7+ | Follow-up searches for dependency types | ~50 each | ~500 each |
| Total | 6-10 calls | ~500 | ~12,000-15,000 |
The grep approach returns a lot of raw file content that the LLM must parse.
LSP approach (estimated ~3-5 tool calls):
| Step | Tool Call | Tokens In (prompt) | Tokens Out (response) |
|---|---|---|---|
| 1 | workspaceSymbol("ChatModel") |
~50 | ~200 (symbol locations) |
| 2 | goToImplementation on ChatModel interface |
~80 | ~150 (implementation locations) |
| 3 | documentSymbol on each implementation |
~50 each | ~200 each (structured outline) |
| 4 | hover on constructor parameters (optional) |
~50 | ~100 (type info) |
| Total | 3-5 calls | ~300 | ~1,000-1,500 |
LSP returns structured, minimal results — just symbol names, types, and locations.
| Metric | Grep/Glob | LSP | Savings |
|---|---|---|---|
| Tool calls per navigation task | 6-10 | 3-5 | 40-60% fewer calls |
| Tokens consumed per task | 12,000-15,000 | 1,000-1,500 | ~90% fewer tokens |
| LLM round-trips | 3-5 turns | 1-2 turns | 50-70% fewer turns |
| Premium requests per task | 3-5 | 1-2 | 50-70% fewer requests |
The token savings come from two sources:
- Less raw content: LSP returns structured metadata, not full file contents
- Fewer iterations: LSP provides precise answers that don't require follow-up searches
Grep result for "find ChatModel implementations":
src/models/openai/OpenAiChatModel.java:45:public class OpenAiChatModel implements ChatModel {
src/models/anthropic/AnthropicChatModel.java:38:public class AnthropicChatModel implements ChatModel {
src/models/ollama/OllamaChatModel.java:52:public class OllamaChatModel implements ChatModel {
... (potentially 20+ lines including false positives from comments, tests, docs)
Token cost: ~500-2,000 tokens depending on number of matches.
LSP goToImplementation result for the same query:
Found 3 implementation(s):
/src/models/openai/OpenAiChatModel.java:45
/src/models/anthropic/AnthropicChatModel.java:38
/src/models/ollama/OllamaChatModel.java:52
Token cost: ~50-100 tokens. Zero false positives.
The LSP formatter in src/lsp/formatters.ts12 produces concise, structured output that costs minimal tokens.
The grep/glob/bash tools work immediately — they are built into Copilot CLI with no configuration required. Ripgrep is bundled with the CLI.
Two installation paths are available:
Option A: Plugin installation (recommended)13
copilot plugin install jdubois/java-lsp-eclipse-jdt-ls
copilot --experimentalThe plugin auto-downloads Eclipse JDT LS on first use14 and handles:
- Java 21+ detection and validation
- Platform-specific configuration (macOS/Linux/Windows)
- JDT LS download from Eclipse milestone server
- Workspace data directory setup
Option B: Manual LSP configuration15
// ~/.copilot/lsp-config.json
{
"lspServers": {
"java": {
"command": "jdtls",
"args": ["-data", "/tmp/jdtls-workspace"],
"fileExtensions": { ".java": "java" }
}
}
}Requirements:
- Java 21+ installed
- Eclipse JDT LS installed (via Homebrew:
brew install jdtls, or manual download) --experimentalflag enabled (LSP is an experimental feature as of March 2026)- ~1 GB memory for the JDT LS process16
| Pros | Cons |
|---|---|
| ✅ Zero setup — works immediately | ❌ No understanding of Java semantics |
| ✅ Fast for one-off text searches | ❌ Cannot resolve types, generics, or inheritance |
| ✅ Works for any file type (not just Java) | ❌ False positives (matches in comments, strings, docs) |
| ✅ No memory overhead | ❌ Requires more LLM round-trips for navigation |
| ✅ No Java version requirement | ❌ Cannot perform semantic rename |
| ✅ No startup delay | ❌ Higher token consumption per navigation task |
| ✅ Reliable on any machine | ❌ Cannot trace call hierarchy |
| ❌ Cannot navigate across Maven/Gradle module boundaries |
| Pros | Cons |
|---|---|
| ✅ True semantic code understanding | ❌ Requires Java 21+ and JDT LS installation |
| ✅ Precise navigation (zero false positives) | ❌ ~1 GB memory for the language server process |
| ✅ 40-60% fewer tool calls per task | ❌ 30-120s initial workspace indexing |
| ✅ ~90% fewer tokens per navigation task | ❌ Experimental feature (requires --experimental) |
| ✅ 50-70% fewer premium requests | ❌ Project needs pom.xml or build.gradle for full features |
| ✅ Understands generics, annotations, inheritance | ❌ Only helps with Java files (use grep for config, XML, etc.) |
| ✅ Semantic rename across all files | ❌ Server can crash on very large monorepos |
| ✅ Call hierarchy analysis | |
| ✅ Javadoc and type information via hover |
| Use Case | Recommended Approach |
|---|---|
| Quick one-off search for a string | Grep |
| Understanding a Spring Boot application's architecture | LSP |
| Finding all usages of a method across a multi-module Maven project | LSP |
| Searching for configuration properties in YAML/properties files | Grep |
| Refactoring — renaming a class or method | LSP |
| Searching for TODO comments | Grep |
| Tracing a bug through a call chain | LSP |
| Working with a mix of Java + non-Java files | Both (LSP for Java, grep for rest) |
| CI/CD environment or constrained system | Grep (no JVM overhead) |
| Production Java development on a regular workstation | LSP |
Install the LSP plugin. The setup takes 2 minutes, and the benefits in accuracy, token savings, and productivity are substantial:
copilot plugin install jdubois/java-lsp-eclipse-jdt-ls
copilot --experimentalUse both approaches together. The system prompt already instructs the LLM to select the right tool for each task6:
- LSP for semantic navigation (definitions, references, call hierarchy)
- grep for text-based search (strings, patterns, non-Java files)
- glob for file discovery
The LLM will naturally fall back to grep/glob for tasks where LSP doesn't help (searching log messages, finding configuration files, etc.).
The LSP approach can reduce premium request usage by 50-70% per code navigation session compared to the grep-only approach. If you're on a limited Copilot quota, LSP is the highest-impact optimization you can make for Java projects.
| Claim | Confidence | Basis |
|---|---|---|
| Tool priority order (LSP > grep > glob > shell) | High | Directly from system prompt source code6 |
| LSP operations and formatting | High | Directly from lsp.ts and formatters.ts source512 |
| Token savings estimates (50-90%) | Medium-High | Based on output format analysis; exact savings depend on task complexity and LLM behavior |
| Premium request reduction (50-70%) | Medium | Estimated from tool call count reduction; actual savings depend on conversation patterns |
| JDT LS startup time (30-120s for indexing) | Medium | Based on documented behavior10 and project size analysis (394K lines in Spring AI); actual time varies by hardware |
| LSP memory usage (~1 GB) | High | Documented in configuration comparison table16 |
| Plugin auto-download behavior | High | Directly from launcher script source code14 |
Footnotes
-
Grep tool implementation:
copilot-agent-runtime/src/tools/grep.ts:63-227— Uses ripgrep with--hidden,--with-filename, and file type filtering ↩ -
Glob tool implementation:
copilot-agent-runtime/src/tools/glob.ts:30-130— Uses ripgrep--filesmode for fast file discovery ↩ -
Eclipse JDT LS Repository — The same engine powering VS Code's Java support ↩
-
LSP Client factory with singleton pattern and client pooling:
copilot-agent-runtime/src/lsp/LSPClient.ts:75-146↩ -
LSP tool implementation with 9 operations:
copilot-agent-runtime/src/tools/lsp.ts:36-46(operation enum), lines300-835(full implementation) ↩ ↩2 -
System prompt tool preference order:
copilot-agent-runtime/src/prompts/cli/system.ts:215— "code intelligence tools > LSP-based tools > glob > grep > shell" ↩ ↩2 ↩3 -
Dynamic LSP detection in code search instructions:
copilot-agent-runtime/src/prompts/shared/tools.ts:92-129↩ -
LSP section prepended to system prompt when available:
copilot-agent-runtime/src/prompts/shared/tools.ts:104-111↩ -
Explore agent includes LSP in tools list:
copilot-agent-runtime/src/agents/definitions/explore.agent.yaml(line 18) ↩ -
JDT LS troubleshooting guide on slow startup:
java-lsp-eclipse-jdt-ls/skills/eclipse-jdtls-setup/SKILL.mdandlsp-config.md:265— "Normal — jdtls indexes workspace. Use persistent -data dir" ↩ ↩2 -
JDT LS launcher with Java options (-Xms512m, -Xmx2G):
java-lsp-eclipse-jdt-ls/bin/launch-jdtls:63-76↩ -
LSP result formatters for concise LLM output:
copilot-agent-runtime/src/lsp/formatters.ts:1-150↩ ↩2 -
Java LSP plugin README installation instructions:
java-lsp-eclipse-jdt-ls/README.md:18-26↩ -
Auto-download logic in launcher helper:
java-lsp-eclipse-jdt-ls/bin/lib/common.sh:59-97— Downloads from Eclipse milestone server ↩ ↩2 -
Manual LSP configuration format:
lsp-config.md:21-35↩ -
JDT LS memory usage (~1 GB):
lsp-config.md:96— Comparison table showing "Memory: ~1 GB" for Eclipse JDT LS ↩ ↩2
IDEA 2026.1 brings a massive upgrade to its MCP Server. Exposing granular tools like rename_refactoring and search_symbol means AI agents can finally leverage the IDE's actual engine instead of just doing plain text replacements.
