Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jdubois/b06ee4b9471e9a3cc5351c8df4332553 to your computer and use it in GitHub Desktop.

Select an option

Save jdubois/b06ee4b9471e9a3cc5351c8df4332553 to your computer and use it in GitHub Desktop.
Java Development with GitHub Copilot CLI: Default (Grep/Tools) vs. Java LSP (Eclipse JDT LS)

Java Development with GitHub Copilot CLI: Default (Grep/Tools) vs. Java LSP (Eclipse JDT LS)

Executive Summary

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.


1. How Each Approach Works

1.1 Default Approach: Grep, Glob, and Shell Tools

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:

  1. The LLM uses glob to find files: glob("**/UserService.java")
  2. It uses grep to search for symbols: grep("class UserService", type: "java")
  3. It reads matching files with view to understand code
  4. It iterates — searching for related classes, interfaces, imports
  5. 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.

1.2 LSP Approach: Eclipse JDT Language Server

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:

  1. The LLM uses workspaceSymbol("UserService") → gets exact location
  2. It uses documentSymbol on the file → gets full class structure (methods, fields, constructors)
  3. It uses goToDefinition to navigate to specific types → instant navigation
  4. It uses findReferences to find all usages → complete, accurate list
  5. It uses incomingCalls / outgoingCalls for 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.


2. How Copilot CLI Selects Tools

2.1 System Prompt Tool Priority

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.

2.2 Dynamic LSP Detection

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

2.3 Explore Agent Also Gets LSP

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.


3. Detailed Comparison

3.1 Accuracy & Quality of Results

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).

3.2 Speed

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.

3.3 Token Usage & Premium Requests

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.

Token Savings Estimate

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:

  1. Less raw content: LSP returns structured metadata, not full file contents
  2. Fewer iterations: LSP provides precise answers that don't require follow-up searches

3.4 Result Formatting Comparison

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.


4. Setup & Configuration Complexity

4.1 Default Approach (Zero Setup)

The grep/glob/bash tools work immediately — they are built into Copilot CLI with no configuration required. Ripgrep is bundled with the CLI.

4.2 LSP Approach (Moderate Setup)

Two installation paths are available:

Option A: Plugin installation (recommended)13

copilot plugin install jdubois/java-lsp-eclipse-jdt-ls
copilot --experimental

The 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)
  • --experimental flag enabled (LSP is an experimental feature as of March 2026)
  • ~1 GB memory for the JDT LS process16

5. Pros and Cons Summary

Default Approach (Grep/Glob/Shell)

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

LSP Approach (Eclipse JDT LS)

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

6. When to Use Which

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

7. Recommendations

For Day-to-Day Java Development

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 --experimental

For Best Results

Use 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.).

For Token-Conscious Users

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.


Confidence Assessment

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

Footnotes

  1. Grep tool implementation: copilot-agent-runtime/src/tools/grep.ts:63-227 — Uses ripgrep with --hidden, --with-filename, and file type filtering

  2. Glob tool implementation: copilot-agent-runtime/src/tools/glob.ts:30-130 — Uses ripgrep --files mode for fast file discovery

  3. Eclipse JDT LS Repository — The same engine powering VS Code's Java support

  4. LSP Client factory with singleton pattern and client pooling: copilot-agent-runtime/src/lsp/LSPClient.ts:75-146

  5. LSP tool implementation with 9 operations: copilot-agent-runtime/src/tools/lsp.ts:36-46 (operation enum), lines 300-835 (full implementation) 2

  6. 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

  7. Dynamic LSP detection in code search instructions: copilot-agent-runtime/src/prompts/shared/tools.ts:92-129

  8. LSP section prepended to system prompt when available: copilot-agent-runtime/src/prompts/shared/tools.ts:104-111

  9. Explore agent includes LSP in tools list: copilot-agent-runtime/src/agents/definitions/explore.agent.yaml (line 18)

  10. JDT LS troubleshooting guide on slow startup: java-lsp-eclipse-jdt-ls/skills/eclipse-jdtls-setup/SKILL.md and lsp-config.md:265 — "Normal — jdtls indexes workspace. Use persistent -data dir" 2

  11. JDT LS launcher with Java options (-Xms512m, -Xmx2G): java-lsp-eclipse-jdt-ls/bin/launch-jdtls:63-76

  12. LSP result formatters for concise LLM output: copilot-agent-runtime/src/lsp/formatters.ts:1-150 2

  13. Java LSP plugin README installation instructions: java-lsp-eclipse-jdt-ls/README.md:18-26

  14. Auto-download logic in launcher helper: java-lsp-eclipse-jdt-ls/bin/lib/common.sh:59-97 — Downloads from Eclipse milestone server 2

  15. Manual LSP configuration format: lsp-config.md:21-35

  16. JDT LS memory usage (~1 GB): lsp-config.md:96 — Comparison table showing "Memory: ~1 GB" for Eclipse JDT LS 2

@EastLord

Copy link
Copy Markdown

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.
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment