Skip to content

JDTLS spawns redundant per-module processes in Java monorepos, causing OOM #11368

Closed

Description

@csaroff

Summary

In Java monorepos (e.g. Gradle multi-module projects), opencode spawns a separate jdtls process per submodule that the agent visits. Each process uses ~200-300MB of RSS, so a session where the agent explores 5 submodules accumulates ~1.2GB of jdtls processes. Across multiple terminal sessions this quickly leads to OOM.

Root cause

The JDTLS root function uses NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]) (server.ts:1132). This walks up from the file being edited and returns the nearest directory containing a build file.

In a monorepo, every submodule has its own build.gradle or pom.xml, so each submodule the agent touches resolves to a different root. Since LSP client deduplication keys on root + serverID (index.ts:231), each unique root spawns a new jdtls process.

This is unnecessary — jdtls natively supports multi-module projects. Pointing it at the monorepo root (where settings.gradle or gradlew lives) lets it discover and handle all submodules with a single process.

Observed behavior

On a Gradle monorepo with ~100 submodules, 2 terminal sessions accumulated 5 jdtls processes:

PID 6789  → cwd: entry/foo
PID 9070  → cwd: entry/bar/qux
PID 13287 → cwd: entry/bar/corge
PID 13292 → cwd: entry/foo  (duplicate, different session)
PID 17572 → cwd: entry/baz

Each has a unique -data temp dir and ~200-300MB RSS.

Expected behavior

A single jdtls process rooted at the monorepo root, regardless of how many submodules the agent visits.

Suggested fix

Give JDTLS the same root-finding strategy that KotlinLS already uses (server.ts:1234-1245) — prefer settings.gradle / settings.gradle.kts / gradlew (monorepo root markers) before falling back to build.gradle / pom.xml:

root: async (file) => {
  const settingsRoot = await NearestRoot(["settings.gradle.kts", "settings.gradle"])(file)
  if (settingsRoot) return settingsRoot
  const wrapperRoot = await NearestRoot(["gradlew", "gradlew.bat"])(file)
  if (wrapperRoot) return wrapperRoot
  return NearestRoot(["build.gradle.kts", "build.gradle", "pom.xml", ".project", ".classpath"])(file)
},

Related

Related to #7227 (LSP spawning for external directories) — both issues stem from the same pattern of spawning redundant LSP servers when a single instance would suffice.

Environment

  • opencode v1.1.43 (Homebrew)
  • macOS, Java 21

Activity

  1. added
    perfIndicates a performance issue or need for optimization
    on Jan 30, 2026
  2. ainslec commented on Feb 1, 2026

    @ainslec

    Having what looks like the same issue, multiple java processes, in my case 7 of about 1.6GB each. Leading to OOM exceptions on my machine when editing a single Java project on a PC with nothing else loaded apart from a browser with 5 tabs (with 32GB RAM).

    for p in 1155775 1255497 1255498 1255499 1255501 1255503 1255604; do   echo "PID $p parent:";   ps -o pid,ppid,cmd -p "$p";   ps -o pid,cmd -p "$(ps -o ppid= -p "$p")";   echo; done
    PID 1155775 parent:
        PID    PPID CMD
    1155775 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-data2ngpWP -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255497 parent:
        PID    PPID CMD
    1255497 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-datacyJ3wK -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255498 parent:
        PID    PPID CMD
    1255498 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-dataUxDFwY -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255499 parent:
        PID    PPID CMD
    1255499 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-dataGA2E7m -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255501 parent:
        PID    PPID CMD
    1255501 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-dataDB0baj -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255503 parent:
        PID    PPID CMD
    1255503 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-dataTpz6UD -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
    PID 1255604 parent:
        PID    PPID CMD
    1255604 1025264 /usr/bin/java -jar /home/chris/.local/share/opencode/bin/jdtls/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar -configuration /home/chris/.local/share/opencode/bin/jdtls/config_linux -data /tmp/opencode-jdtls-dataaYBZ7t -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -Dlog.level=ALL --add-modules=ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
        PID CMD
    1025264 /usr/bin/conmon --api-version 1 -c 165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a -u c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -r /usr/bin/crun -b /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc -p /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60d86f6a/userdata/c4366cb35715c04156bc647fbc88c62ad6f641aecbb1c5e4ef42a79df461fadc/exec_pid -n java --exit-dir /var/home/chris/.local/share/containers/storage/overlay-containers/165d6c618f3d1366d305aca5accc0dbe8ed79db1c1668ccd16a6cc5b60
    
  3. akumaburn commented on Feb 26, 2026

    @akumaburn

    Same issue, ran out of 128GB of ram pretty fast, this is a major bug.

  4. thijslemmens commented on Mar 3, 2026

    @thijslemmens

    I worked around this by writing a wrapper script for jdtls. I use this as the java lsp instead of the OOTB. I can share if people are interested.

  5. fabians-px commented on Mar 3, 2026

    @fabians-px

    @thijslemmens absolutely!

  6. thijslemmens commented on Mar 3, 2026

    @thijslemmens

    @fabians-px

    jdtls-gradle

    #!/usr/bin/env bash
    set -euo pipefail
    
    find_gradle_root() {
        local dir="$(pwd)"
        while [ "$dir" != "/" ]; do
            if [ -f "$dir/settings.gradle" ] || [ -f "$dir/settings.gradle.kts" ]; then
                echo "$dir"
                return 0
            fi
            if [ -f "$dir/gradlew" ]; then
                echo "$dir"
                return 0
            fi
            dir="$(dirname "$dir")"
        done
        echo "$(pwd)"
    }
    
    GRADLE_ROOT=$(find_gradle_root)
    
    FOLDER_NAME=$(basename "$GRADLE_ROOT")
    PATH_HASH=$(echo "$GRADLE_ROOT" | md5sum | cut -c1-8)
    
    WORKSPACE="$HOME/.cache/jdtls-workspace/${FOLDER_NAME}-${PATH_HASH}"
    mkdir -p "$WORKSPACE"
    
    exec ~/.local/bin/jdtls -data "$WORKSPACE" "$@"  

    jdtls

    #!/usr/bin/env bash
    
    # JDTLS launcher script
    JDTLS_HOME="$HOME/.local/share/jdtls"
    
    # Determine the Java command to use
    if [ -n "$JAVA_HOME" ]; then
        JAVA="$JAVA_HOME/bin/java"
    else
        JAVA="java"
    fi
    
    # Find the JDTLS launcher JAR
    LAUNCHER=$(find "$JDTLS_HOME/plugins" -name 'org.eclipse.equinox.launcher_*.jar' | sort | tail -n 1)
    
    if [ -z "$LAUNCHER" ]; then
        echo "Error: Could not find JDTLS launcher JAR in $JDTLS_HOME/plugins" >&2
        exit 1
    fi
    
    # Determine the configuration directory based on OS
    case "$(uname -s)" in
        Linux*)     CONFIG_DIR="$JDTLS_HOME/config_linux";;
        Darwin*)    CONFIG_DIR="$JDTLS_HOME/config_mac";;
        *)          CONFIG_DIR="$JDTLS_HOME/config_linux";;
    esac
    
    # Create a workspace data directory
    WORKSPACE_DATA="${XDG_CACHE_HOME:-$HOME/.cache}/jdtls-workspace"
    mkdir -p "$WORKSPACE_DATA"
    
    # Launch JDTLS
    exec "$JAVA" \
        -javaagent:$HOME/.local/share/java/lombok.jar \
        -Declipse.application=org.eclipse.jdt.ls.core.id1 \
        -Dosgi.bundles.defaultStartLevel=4 \
        -Declipse.product=org.eclipse.jdt.ls.core.product \
        -Dlog.level=ALL \
        -noverify \
        -Xmx1G \
        --add-modules=ALL-SYSTEM \
        --add-opens java.base/java.util=ALL-UNNAMED \
        --add-opens java.base/java.lang=ALL-UNNAMED \
        -jar "$LAUNCHER" \
        -configuration "$CONFIG_DIR" \
        -data "$WORKSPACE_DATA" \
        "$@"

    opencode.json

    {
    ...
    "lsp": {
        "jdtls": {
          "disabled": true
        },
        "java": {
          "command": ["jdtls-gradle"],
          "extensions": [".java"]
        }
      }
    }

    You need to install jdtls manually. I used https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/1.57.0/jdt-language-server-1.57.0-202602261110.tar.gz

  7. jiayaoO3O commented on Mar 6, 2026

    @jiayaoO3O

    @thijslemmens For Maven projects, is there a way to identify the root directory? README.md?

  8. thijslemmens commented on Mar 6, 2026

    @thijslemmens

    @thijslemmens For Maven projects, is there a way to identify the root directory? README.md?

    Not using maven. I think to do it properly, you have to parse pom files. A workaround could be that you place your own marker file in the root.

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

Metadata

Metadata

Assignees

Labels

perfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions