@@ -34,17 +34,55 @@ export class ProcessWatcher {
3434 if ( ! javaExt ) {
3535 return false ;
3636 }
37- // get embedded JRE Home
37+
38+ // First check java.jdt.ls.java.home setting
3839 let jreHome : string | undefined ;
39- try {
40- const jreFolder = path . join ( javaExt . extensionPath , "jre" ) ;
41- const jreDistros = await fs . promises . readdir ( jreFolder ) ;
42- if ( jreDistros . length > 0 ) {
43- jreHome = path . join ( jreFolder , jreDistros [ 0 ] ) ;
40+ let configJavaHome = vscode . workspace . getConfiguration ( ) . get < string > ( 'java.jdt.ls.java.home' ) ;
41+
42+ // If user has explicitly configured a Java home, use that
43+ if ( configJavaHome ) {
44+ if ( await this . isValidJpsPath ( configJavaHome ) ) {
45+ jreHome = configJavaHome ;
46+ } else {
47+ // Log warning but continue with fallback
48+ console . warn ( `Configured Java home ${ configJavaHome } is not valid or not accessible. Checking other options.` ) ;
49+ }
50+ }
51+
52+ // If not found, check for default runtime in java.configuration.runtimes
53+ if ( ! jreHome ) {
54+ const runtimes = vscode . workspace . getConfiguration ( ) . get < any [ ] > ( 'java.configuration.runtimes' ) ;
55+ if ( Array . isArray ( runtimes ) && runtimes . length > 0 ) {
56+ // First look for one marked as default
57+ const defaultRuntime = runtimes . find ( r => r . default === true ) ;
58+ if ( defaultRuntime && defaultRuntime . path ) {
59+ if ( await this . isValidJpsPath ( defaultRuntime . path ) ) {
60+ jreHome = defaultRuntime . path ;
61+ }
62+ }
63+
64+ // If no default is set or default is invalid, try the first one
65+ if ( ! jreHome && runtimes [ 0 ] . path ) {
66+ if ( await this . isValidJpsPath ( runtimes [ 0 ] . path ) ) {
67+ jreHome = runtimes [ 0 ] . path ;
68+ }
69+ }
70+ }
71+ }
72+
73+ // If no valid JDK is found in settings, fall back to embedded JRE
74+ if ( ! jreHome ) {
75+ try {
76+ const jreFolder = path . join ( javaExt . extensionPath , "jre" ) ;
77+ const jreDistros = await fs . promises . readdir ( jreFolder ) ;
78+ if ( jreDistros . length > 0 ) {
79+ jreHome = path . join ( jreFolder , jreDistros [ 0 ] ) ;
80+ }
81+ } catch ( error ) {
82+ // do nothing when jre is not embedded, to avoid spamming logs
4483 }
45- } catch ( error ) {
46- // do nothing when jre is not embedded, to avoid spamming logs
4784 }
85+
4886 if ( ! jreHome ) {
4987 return false ;
5088 }
@@ -96,6 +134,21 @@ export class ProcessWatcher {
96134 return [ y , o ] . join ( os . EOL ) ;
97135 }
98136
137+ private async isValidJpsPath ( jdkPath : string ) : Promise < boolean > {
138+ try {
139+ // Check if path exists
140+ await fs . promises . access ( jdkPath , fs . constants . R_OK ) ;
141+
142+ // Check if the jps tool exists in the bin directory
143+ const jpsPath = path . join ( jdkPath , "bin" , "jps" + ( os . platform ( ) === 'win32' ? '.exe' : '' ) ) ;
144+ await fs . promises . access ( jpsPath , fs . constants . X_OK ) ;
145+
146+ return true ;
147+ } catch ( err ) {
148+ return false ;
149+ }
150+ }
151+
99152 private onDidJdtlsCrash ( lastHeartbeat ?: string ) {
100153 sendInfo ( "" , {
101154 name : "jdtls-last-heartbeat" ,
0 commit comments