You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Status bar keeps showing "100% Starting Java Language Server" #834
The language server has been launched successfully, and all features are ready, but the message is pinned on the status bar. It's probably a racing condition, and it's not stably reproducible.
I've done some investigation to print out the notifications, and I find the sequence on my machine is usually as following:
[Starting] 30% Starting Java Language Server
[Starting] 35% Starting Java Language Server
[Starting] 100% Starting Java Language Server
[Starting] 100% Starting Java Language Server
[Started] Ready
[Starting] 100% Starting Java Language Server
You may notice that after a "started" notification, it still receives a "starting" notification.
But when I met the issue, the log looks like the following, indicating that previous notifications are not received by the callback.
[Starting] 100% Starting Java Language Server
So I guess if it's because: immediately after the languageClient onReady, the previous notifications have been sent, before the callback completes the registration.
I'm not sure if this is the right place, or if I should open a new issue. But this issue seems at least related.
We are trying out vscode-java and org.eclipse.jdt.ls, we are running into a somewhat similar issue. Among other things, we are trying to "attach" VS Code to our running Eclipse process. Whenever we try to attach VS Code for a 2nd time to the same server (e.g. we restarted VS Code or the extension host process restarted), we see hanging status: "Activating..."
When I debug the server code, both notifications for Started and ServiceReady are sent. But only ServiceReady is received in the languageClient.onNotification() function.
If I delay the registration of the listener, both notifications are missed:
I.e. if the server is fast to send its notifications (which is the case for what we are trying), notifications can be missed.
Any advice on what needs to change in vscode-java to prevent this? Unfortunately Typescript and VS Code extensions are both very new to me. Can languageClient.onNotification() be registered before the standardClient.start() call? Even before the initialize call, just to be sure notifications are not missed.
This is a genuine, long-lived race condition, and I can show it was fixed only very recently.
Issue #834: Status bar stuck on "100% Starting Java Language Server"
The bug, as diagnosed by both the reporter and a commenter (trancexpress): vscode-java registers its onNotification(StatusNotification...) handler (which drives the status bar through Starting → Started/ServiceReady) after standardClient.start() resolves, with an await of something else interposed in between. If jdt.ls fires its Started/ServiceReady notifications fast (e.g. a lightweight project, or in the reporter's case, attaching to an already-running server), those notifications arrive and are dropped before the handler exists to catch them — leaving the status bar permanently stuck.
trancexpress even pinpointed the exact offending pattern and asked the right question: "Can languageClient.onNotification() be registered before the standardClient.start() call?"
What I found in the current code (src/extension.ts, doStartStandardServer): this exact bug pattern was present until commit 8fc5f11, dated 2026-10-01 — about a month ago — "Add java.import.mode setting for on-demand project import." Buried in that commit (as a side-effect fix, not the headline feature) is this change:
standardClient.start().then(async () => {
- standardClient.registerLanguageClientActions(context, await fse.pathExists(path.join(workspacePath, ".metadata",
".plugins")), jdtEventEmitter);
+ // Register handlers immediately (no await before) so they are ready
+ // when the server sends synchronizeBundles and ServiceReady right after 'initialized'.
+ // In on-demand mode with no Java file open, zero projects are loaded at startup,
+ // so the server reaches synchronizeBundles so fast that an await here would cause
+ // the ExecuteClientCommandRequest handler to not be registered in time.
+ const hasImportedPromise = fse.pathExists(path.join(workspacePath, ".metadata", ".plugins"));
+ standardClient.registerLanguageClientActions(context, hasImportedPromise, jdtEventEmitter);
Before this: the await fse.pathExists(...) sat inline as an argument to registerLanguageClientActions(...) — meaning execution yielded the event loop before the notification handlers got registered. That's structurally the same race as the reporter's 2019 setTimeout(f, 1000) repro, just a smaller/implicit window (a disk stat instead of a 1s sleep) — same bug, same mechanism, just harder to hit.
After: the fse.pathExists promise is captured without awaiting it, and registerLanguageClientActions (which synchronously calls onNotification as its first act) runs immediately in the .then() callback with zero yields beforehand. The unawaited promise is passed in and only consumed later, inside the function, after the critical handler is already wired up.
The commit message doesn't mention #834 or race conditions at all — this looks like it was fixed as a necessary side effect of making on-demand import mode work reliably (per the comment: "the server reaches synchronizeBundles so fast that an await here would cause the handler to not be registered in time"), not as a deliberate fix for this ticket.
Triage verdict: real, confirmed-root-caused bug, fixed very recently (Oct 2026) as an incidental fix, not explicitly linked back to #834.
I'm going to close this as likely fixed in 8fc5f11 but anyone still watching this bug should confirm it's fixed in the most recent release.
Activity
trancexpress commentedon Jun 12, 2026
I'm not sure if this is the right place, or if I should open a new issue. But this issue seems at least related.
We are trying out
vscode-javaandorg.eclipse.jdt.ls, we are running into a somewhat similar issue. Among other things, we are trying to "attach" VS Code to our running Eclipse process. Whenever we try to attach VS Code for a 2nd time to the same server (e.g. we restarted VS Code or the extension host process restarted), we see hanging status: "Activating..."This seems to be due to this code:
When I debug the server code, both notifications for
StartedandServiceReadyare sent. But onlyServiceReadyis received in thelanguageClient.onNotification()function.If I delay the registration of the listener, both notifications are missed:
I.e. if the server is fast to send its notifications (which is the case for what we are trying), notifications can be missed.
Any advice on what needs to change in
vscode-javato prevent this? Unfortunately Typescript and VS Code extensions are both very new to me. CanlanguageClient.onNotification()be registered before thestandardClient.start()call? Even before the initialize call, just to be sure notifications are not missed.robstryker commentedon Nov 2, 2026
This is a genuine, long-lived race condition, and I can show it was fixed only very recently.
Issue #834: Status bar stuck on "100% Starting Java Language Server"
The bug, as diagnosed by both the reporter and a commenter (trancexpress): vscode-java registers its onNotification(StatusNotification...) handler (which drives the status bar through Starting → Started/ServiceReady) after standardClient.start() resolves, with an await of something else interposed in between. If jdt.ls fires its Started/ServiceReady notifications fast (e.g. a lightweight project, or in the reporter's case, attaching to an already-running server), those notifications arrive and are dropped before the handler exists to catch them — leaving the status bar permanently stuck.
trancexpress even pinpointed the exact offending pattern and asked the right question: "Can languageClient.onNotification() be registered before the standardClient.start() call?"
What I found in the current code (src/extension.ts, doStartStandardServer): this exact bug pattern was present until commit 8fc5f11, dated 2026-10-01 — about a month ago — "Add java.import.mode setting for on-demand project import." Buried in that commit (as a side-effect fix, not the headline feature) is this change:
Before this: the await fse.pathExists(...) sat inline as an argument to registerLanguageClientActions(...) — meaning execution yielded the event loop before the notification handlers got registered. That's structurally the same race as the reporter's 2019 setTimeout(f, 1000) repro, just a smaller/implicit window (a disk stat instead of a 1s sleep) — same bug, same mechanism, just harder to hit.
After: the fse.pathExists promise is captured without awaiting it, and registerLanguageClientActions (which synchronously calls onNotification as its first act) runs immediately in the .then() callback with zero yields beforehand. The unawaited promise is passed in and only consumed later, inside the function, after the critical handler is already wired up.
The commit message doesn't mention #834 or race conditions at all — this looks like it was fixed as a necessary side effect of making on-demand import mode work reliably (per the comment: "the server reaches synchronizeBundles so fast that an await here would cause the handler to not be registered in time"), not as a deliberate fix for this ticket.
Triage verdict: real, confirmed-root-caused bug, fixed very recently (Oct 2026) as an incidental fix, not explicitly linked back to #834.
I'm going to close this as likely fixed in 8fc5f11 but anyone still watching this bug should confirm it's fixed in the most recent release.