Skip to content

Spring Boot Language Server: severe CPU/memory issues in large multi-module workspaces (500-900% CPU, thread explosion, NPE) #1894

@gaogage

Description

@gaogage

Environment

  • IDE: VSCode on macOS 26.5 (ARM64)
  • Extension: vmware.vscode-spring-boot v2.1.2 and v2.2.0 (both affected)
  • Java: OpenJDK 21.0.11-tem (Temurin)
  • LS Config: -Xmx2g, G1GC, MaxMetaspaceSize=384m
  • Workspace: 26 sub-projects (multi-module Maven, ~15 Spring Boot modules)

Summary

The Spring Boot Language Server exhibits three distinct but compounding problems in large multi-module workspaces, resulting in sustained 500-900% CPU usage (8+ cores) and 1.5-4GB RSS:

1. CachedThreadPool Explosion During Initial Indexing

During SpringIndexerJava.initializeProject(), the LS uses a CachedThreadPool (pool-11, backed by SynchronousQueue) for parallel project scanning. With 26 projects, this pool creates 500+ threads (observed up to #536), each consuming 60-100 CPU-seconds. The threads are created faster than they complete, causing thread churn and massive context-switching overhead.

Observed: pool-11-thread-536, each thread 60-100s CPU, 136+ concurrent threads

2. CompositeASTVisitor NPE (JdtCodeActionHandler)

Repeated NullPointerException in CompositeASTVisitor.visit(QualifiedName) — the astVisitor is null when processing Java code actions. This occurs frequently during normal editing (observed 150+ times in logs), and may trigger cascading re-indexing as a fallback.

java.lang.NullPointerException: Cannot invoke "org.eclipse.jdt.core.dom.ASTVisitor.visit(
  org.eclipse.jdt.core.dom.QualifiedName)" because "astVisitor" is null
    at org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor.visit(
      CompositeASTVisitor.java:176)

3. SpringMetamodelIndex Full Traversal on Every UI Interaction

Every VSCode UI interaction (file tab switch, Spring Explorer refresh, document focus change) triggers SpringMetamodelIndex.getBeansOfDocument()getNodesOfType() which traverses the entire Spring metamodel index for all 26 projects. In v2.2.0, JMoleculesStructureView.createTree() adds an additional traversal layer. This repeats every few minutes as the user navigates code.

Observed stack:

SpringIndexCommands.lambda$new$3() → nodeFrom()
  → JsonNodeHandler.handleType() → createTypeSubnotes()
    → SpringMetamodelIndex.getBeansOfDocument()
      → getNodesOfType() → getChildren()  // full traversal

4. GC Tail Effect Amplifies Spike Duration

The indexing burst creates 400+ G1 young regions (~400MB of short-lived objects). After business threads complete, G1 concurrent marking threads (G1 Conc#0/#1) run for 10-15 additional minutes at 38% CPU each, extending perceived spike duration.

Steps to Reproduce

  1. Create a VSCode workspace with 15+ Spring Boot Maven modules
  2. Open VSCode — observe initial CPU spike (300-700% for 20-40 min)
  3. Open any Java file, wait for LS to settle
  4. Switch to another Java file — observe repeated CPU spike (400-700%)
  5. Check logs: spring-boot-ls.log shows repeated JdtCodeActionHandler NPE errors
  6. Check threads: jcmd <pid> Thread.print shows pool-11 threads numbered Support in_parallel step in Concourse Language Server #300+

Workaround

Disabling the Spring Boot extension (vmware.vscode-spring-boot) eliminates the CPU spikes. JDT LS (redhat.java) remains functional for Java editing.

Suggested Fix Areas

  1. Replace CachedThreadPool with fixed-size bounded thread pool for project indexing
  2. Add null guard in CompositeASTVisitor.visit() for astVisitor
  3. Cache SpringMetamodelIndex query results per document to avoid full re-traversal
  4. Consider lazy/eager tradeoff: build index eagerly but cache query results

Related (but different) Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions