Bug 385738 - Shorten the classpath/modulepath if necessary.

Shorten the classpath and/or modulepath if it gets longer than current
operating system limits.
Depending on the java version, os and launch configuration, the
classpath argument will be replaced by an argument file, a
classpath-only jar or an env variable.
The modulepath is replaced by an argument file if necessary.
If classpath-only jar needs to be used to shorten the classpath, an
explicit error popup ask if user wants to use classpath-only jar.

Change-Id: Ibcc23b6fa5c5baa05d91aef0f03529fb8e598e08
Signed-off-by: Cedric Chabanois <cchabanois@gmail.com>
diff --git a/org.eclipse.jdt.debug.tests/java8/LongClasspath.java b/org.eclipse.jdt.debug.tests/java8/LongClasspath.java
new file mode 100644
index 0000000..0b196af
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/LongClasspath.java
@@ -0,0 +1,7 @@
+public class LongClasspath {
+
+    public static void main(String[] args) {
+    	System.out.println("Hello");
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
index c68f75f..ca6728e 100644
--- a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
+++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
@@ -59,6 +59,7 @@
 	public static final String JAVA_SE_1_6_EE_NAME = "JavaSE-1.6";
 	public static final String JAVA_SE_1_7_EE_NAME = "JavaSE-1.7";
 	public static final String JAVA_SE_1_8_EE_NAME = "JavaSE-1.8";
+	public static final String JAVA_SE_9_EE_NAME = "JavaSE-9";
 
 	/**
 	 * path to the test src for 'testprograms'
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar
new file mode 100644
index 0000000..401654f
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar
Binary files differ
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java
new file mode 100644
index 0000000..188c640
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java
@@ -0,0 +1,3 @@
+module test.classpath {
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java
new file mode 100644
index 0000000..2c24818
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java
@@ -0,0 +1,9 @@
+package test.classpath;
+
+public class Main {
+
+    public static void main(String[] args) {
+    	System.out.println("Hello world");
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar b/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar
new file mode 100644
index 0000000..401654f
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar
Binary files differ
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java b/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java
new file mode 100644
index 0000000..2c24818
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java
@@ -0,0 +1,9 @@
+package test.classpath;
+
+public class Main {
+
+    public static void main(String[] args) {
+    	System.out.println("Hello world");
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 9a50482..1c8e529 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -84,6 +84,7 @@
 import org.eclipse.jdt.debug.tests.core.WorkspaceSourceContainerTests;
 import org.eclipse.jdt.debug.tests.eval.GeneralEvalTests;
 import org.eclipse.jdt.debug.tests.eval.GenericsEvalTests;
+import org.eclipse.jdt.debug.tests.launching.ClasspathShortenerTests;
 import org.eclipse.jdt.debug.tests.launching.ConfigurationEncodingTests;
 import org.eclipse.jdt.debug.tests.launching.ConfigurationResourceMappingTests;
 import org.eclipse.jdt.debug.tests.launching.ContributedTabTests;
@@ -94,6 +95,8 @@
 import org.eclipse.jdt.debug.tests.launching.LaunchShortcutTests;
 import org.eclipse.jdt.debug.tests.launching.LaunchTests;
 import org.eclipse.jdt.debug.tests.launching.LaunchesTests;
+import org.eclipse.jdt.debug.tests.launching.LongClassPathTests;
+import org.eclipse.jdt.debug.tests.launching.LongModulePathTests;
 import org.eclipse.jdt.debug.tests.launching.MigrationDelegateTests;
 import org.eclipse.jdt.debug.tests.launching.PListParserTests;
 import org.eclipse.jdt.debug.tests.launching.ProjectClasspathVariableTests;
@@ -345,5 +348,12 @@
 	//add the complete eval suite
 		addTest(new TestSuite(GeneralEvalTests.class));
 		//addTest(EvalTestSuite.suite());
+
+		// long classpath tests
+		addTest(new TestSuite(ClasspathShortenerTests.class));
+		addTest(LongClassPathTests.suite());
+		if (JavaProjectHelper.isJava9Compatible()) {
+			addTest(new TestSuite(LongModulePathTests.class));
+		}
 	}
 }
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java
new file mode 100644
index 0000000..2dee9bf
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.URIUtil;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.debug.tests.connectors.MockLaunch;
+import org.eclipse.jdt.internal.launching.ClasspathShortener;
+
+public class ClasspathShortenerTests extends AbstractDebugTest {
+	private static final String MAIN_CLASS = "my.package.MainClass";
+	private static final String ENCODING_ARG = "-Dfile.encoding=UTF-8";
+	private static final String JAVA_10_PATH = "/usr/lib/jvm/java-10-jdk-amd64/bin/java";
+	private static final String JAVA_8_PATH = "/usr/lib/jvm/java-8-openjdk-amd64/bin/java";
+	private ClasspathShortenerForTest classpathShortener;
+	private String userHome;
+
+	public ClasspathShortenerTests(String name) {
+		super(name);
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		userHome = System.getProperty("user.home");
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		if (classpathShortener != null) {
+			classpathShortener.getProcessTempFiles().forEach(file -> file.delete());
+		}
+		super.tearDown();
+	}
+
+	public void testNoNeedToShortenShortClasspath() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+		// When
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertFalse(result);
+	}
+
+	public void testShortenClasspathWhenCommandLineIsTooLong() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+		// When
+		classpathShortener.setMaxCommandLineLength(100);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+	}
+
+	public void testShortenClasspathWhenClasspathArgumentIsTooLong() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+		// When
+		classpathShortener.setMaxArgLength(40);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+	}
+
+	public void testForceClasspathOnlyJar() throws Exception {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+		// When
+		classpathShortener.setForceUseClasspathOnlyJar(true);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(1, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+				MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+		List<File> classpathJars = getClasspathJarsFromJarManifest(classpathShortener.getProcessTempFiles().get(0));
+		assertEquals(new File(userHomePath("/workspace/myProject/bin")), classpathJars.get(0).getCanonicalFile());
+		assertEquals(new File(userHomePath("/workspace/myProject/lib/lib 1.jar")), classpathJars.get(1).getCanonicalFile());
+	}
+
+	public void testArgFileUsedForLongClasspathOnJava9() throws Exception {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(1, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+				MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+		assertEquals("-classpath " + classpath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+	}
+
+	public void testArgFileUsedForLongModulePath() throws Exception {
+		// Given
+		String modulepath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-p", modulepath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "10.0.1", cmdLine, 4, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(1, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+				MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+		assertEquals("--module-path " + modulepath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+	}
+
+	public void testLongClasspathAndLongModulePath() throws Exception {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/lib/lib 2.jar"), userHomePath("/workspace/myProject/lib/lib 3.jar"));
+		String modulepath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-cp", classpath, "-p", modulepath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "10.0.1", cmdLine, 6, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(2, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+				"@" + classpathShortener.getProcessTempFiles().get(1).getAbsolutePath(), MAIN_CLASS, "-arg1",
+				"arg2" }, classpathShortener.getCmdLine());
+		assertEquals("-classpath " + classpath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+		assertEquals("--module-path " + modulepath, getFileContents(classpathShortener.getProcessTempFiles().get(1)));
+	}
+
+	public void testClasspathOnlyJarUsedForLongClasspathOnJava8() throws Exception {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "1.8.0_171", cmdLine, 4, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		classpathShortener.setAllowToUseClasspathOnlyJar(true);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(1, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+				MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+		List<File> classpathJars = getClasspathJarsFromJarManifest(classpathShortener.getProcessTempFiles().get(0));
+		assertEquals(new File(userHomePath("/workspace/myProject/bin")), classpathJars.get(0).getCanonicalFile());
+		assertEquals(new File(userHomePath("/workspace/myProject/lib/lib 1.jar")), classpathJars.get(1).getCanonicalFile());
+	}
+
+	public void testClasspathEnvVariableUsedForLongClasspathOnJava8OnWindows() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		Map<String, String> nativeEnvironment = new LinkedHashMap<>();
+		nativeEnvironment.put("PATH", "C:\\WINDOWS\\System32;C:\\WINDOWS");
+		classpathShortener.setNativeEnvironment(nativeEnvironment);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(0, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { "PATH=C:\\WINDOWS\\System32;C:\\WINDOWS", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+		assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+	}
+
+	public void testClasspathUsedOnWindowsWhenEnvp() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		String[] envp = new String[] { "MYVAR1=value1", "MYVAR2=value2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, envp);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(0, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { "MYVAR1=value1", "MYVAR2=value2", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+		assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+	}
+
+	public void testClasspathInEnvironmentReplacedOnWindows() {
+		// Given
+		String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+		String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+		classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, null);
+		classpathShortener.setMaxCommandLineLength(100);
+
+		// When
+		Map<String, String> nativeEnvironment = new LinkedHashMap<>();
+		nativeEnvironment.put("PATH", "C:\\WINDOWS\\System32;C:\\WINDOWS");
+		nativeEnvironment.put("CLASSPATH", "C:\\myJars\\jar1.jar");
+		classpathShortener.setNativeEnvironment(nativeEnvironment);
+		boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+		// Then
+		assertTrue(result);
+		assertEquals(0, classpathShortener.getProcessTempFiles().size());
+		assertArrayEquals(new String[] { "PATH=C:\\WINDOWS\\System32;C:\\WINDOWS", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+		assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+	}
+
+	private String getFileContents(File file) throws UnsupportedEncodingException, IOException {
+		return new String(Files.readAllBytes(file.toPath()), "UTF-8");
+	}
+
+	private String getClasspathAttributeFromJarManifest(File jarFile) throws FileNotFoundException, IOException {
+		try (JarInputStream jarStream = new JarInputStream(new FileInputStream(jarFile))) {
+			Manifest mf = jarStream.getManifest();
+			return mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
+		}
+	}
+
+	private List<File> getClasspathJarsFromJarManifest(File jarFile) throws FileNotFoundException, IOException {
+		File parentDir = jarFile.getParentFile();
+		String classpath = getClasspathAttributeFromJarManifest(jarFile);
+		return Arrays.stream(classpath.split(" ")).map(relativePath -> {
+			try {
+				return new File(URIUtil.makeAbsolute(new URI(relativePath), parentDir.toURI()));
+			} catch (URISyntaxException e) {
+				throw new RuntimeException(e);
+			}
+		}).collect(Collectors.toList());
+	}
+
+	private String userHomePath(String path) {
+		return userHome + path;
+	}
+
+	private String getClasspathOrModulePath(String... classpathElements) {
+		return String.join(";", classpathElements);
+	}
+
+	private static class ClasspathShortenerForTest extends ClasspathShortener {
+		private boolean forceUseClasspathOnlyJar = false;
+		private boolean allowToUseClasspathOnlyJar = false;
+		private int maxArgLength = Integer.MAX_VALUE;
+		private int maxCommandLineLength = Integer.MAX_VALUE;
+		private Map<String, String> nativeEnvironment = new HashMap<>();
+
+		public ClasspathShortenerForTest(String os, String javaVersion, String[] cmdLine, int classpathArgumentIndex, String[] envp) {
+			super(os, javaVersion, new MockLaunch(), cmdLine, classpathArgumentIndex, null, envp);
+		}
+
+		public void setAllowToUseClasspathOnlyJar(boolean allowToUseClasspathOnlyJar) {
+			this.allowToUseClasspathOnlyJar = allowToUseClasspathOnlyJar;
+		}
+
+		public void setForceUseClasspathOnlyJar(boolean forceUseClasspathOnlyJar) {
+			this.forceUseClasspathOnlyJar = forceUseClasspathOnlyJar;
+		}
+
+		@Override
+		protected String getLaunchConfigurationName() {
+			return "launch";
+		}
+
+		@Override
+		protected String getLaunchTimeStamp() {
+			return Long.toString(System.currentTimeMillis());
+		}
+
+		@Override
+		protected boolean handleClasspathTooLongStatus() throws CoreException {
+			return allowToUseClasspathOnlyJar;
+		}
+
+		@Override
+		protected boolean getLaunchConfigurationUseClasspathOnlyJarAttribute() throws CoreException {
+			return forceUseClasspathOnlyJar;
+		}
+
+		@Override
+		protected int getMaxArgLength() {
+			return maxArgLength;
+		}
+
+		@Override
+		protected int getMaxCommandLineLength() {
+			return maxCommandLineLength;
+		}
+
+		public void setMaxArgLength(int maxArgLength) {
+			this.maxArgLength = maxArgLength;
+		}
+
+		public void setMaxCommandLineLength(int maxCommandLineLength) {
+			this.maxCommandLineLength = maxCommandLineLength;
+		}
+
+		public void setNativeEnvironment(Map<String, String> nativeEnvironment) {
+			this.nativeEnvironment = nativeEnvironment;
+		}
+
+		@Override
+		protected Map<String, String> getNativeEnvironment() {
+			return nativeEnvironment;
+		}
+
+		@Override
+		protected char getPathSeparatorChar() {
+			// always use ';' as separator (tests would fail on windows otherwise with paths c:\ ...)
+			return ';';
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java
new file mode 100644
index 0000000..758bcac
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.LaunchingPlugin;
+import org.eclipse.jdt.launching.AbstractVMInstall;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.JavaRuntime;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test long classpaths. OSs have limits in term of command line length or argument length. We handle this limit differently depending on the VM
+ * version and OS.
+ *
+ */
+public class LongClassPathTests extends AbstractDebugTest {
+	private static final String MAIN_TYPE_NAME = "test.classpath.Main";
+	private static final IPath CLASSPATH_PROJECT_CONTENT_PATH = new Path("testresources/classpathProject");
+	private IJavaProject javaProject;
+	private ILaunchConfiguration launchConfiguration;
+	private IJavaThread thread;
+
+	public LongClassPathTests(String name) {
+		super(name);
+	}
+
+	public static Test suite() {
+		TestSuite suite = new TestSuite();
+		suite.addTest(new LongClassPathTests("testVeryLongClasspathWithClasspathOnlyJar"));
+		if (JavaProjectHelper.isJava9Compatible()) {
+			suite.addTest(new LongClassPathTests("testVeryLongClasspathWithArgumentFile"));
+		} else if (Platform.getOS().equals(Platform.OS_WIN32)) {
+			suite.addTest(new LongClassPathTests("testVeryLongClasspathWithEnvironmentVariable"));
+		}
+		return suite;
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		try {
+			if (thread != null) {
+				terminateAndRemove(thread);
+			}
+			if (javaProject != null) {
+				javaProject.getProject().delete(true, true, null);
+			}
+			if (launchConfiguration != null) {
+				launchConfiguration.delete();
+			}
+		} catch (CoreException ce) {
+			// ignore
+		} finally {
+			super.tearDown();
+		}
+	}
+
+	/*
+	 * When classpathOnlyJar is enabled, a classpath-only jar is created.
+	 */
+	public void testVeryLongClasspathWithClasspathOnlyJar() throws Exception {
+		// Given
+		javaProject = createJavaProjectClone("testVeryLongClasspathWithClasspathOnlyJar", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_1_6_EE_NAME, true);
+		launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+		int minClasspathLength = 300000;
+		setLongClasspath(javaProject, minClasspathLength);
+		launchConfiguration = enableClasspathOnlyJar(launchConfiguration);
+		waitForBuild();
+
+		// When
+		thread = launchAndSuspend(launchConfiguration);
+
+		// Then
+		File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+		assertTrue(tempFile.exists());
+		assertTrue(tempFile.getName().endsWith(".jar"));
+		String actualClasspath = doEval(thread, "System.getProperty(\"java.class.path\")").getValueString();
+		assertTrue(actualClasspath.contains(tempFile.getAbsolutePath()));
+		assertTrue(actualClasspath.length() < minClasspathLength);
+
+		// When
+		resumeAndExit(thread);
+
+		// Then
+		if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+			// On windows, temp file deletion may fail
+			assertFalse(tempFile.exists());
+		}
+	}
+
+	/*
+	 * When JVM > 9, an argument file for the classpath is created when classpath is too long
+	 */
+	public void testVeryLongClasspathWithArgumentFile() throws Exception {
+		javaProject = createJavaProjectClone("testVeryLongClasspathWithArgumentFile", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_9_EE_NAME, true);
+		launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+		assumeTrue(isArgumentFileSupported(launchConfiguration));
+		int minClasspathLength = 300000;
+
+		// Given
+		setLongClasspath(javaProject, minClasspathLength);
+		waitForBuild();
+
+		// When
+		thread = launchAndSuspend(launchConfiguration);
+
+		// Then
+		File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+		assertTrue(tempFile.exists());
+		assertTrue(tempFile.getName().endsWith(".txt"));
+		assertTrue(doEval(thread, "System.getProperty(\"java.class.path\")").getValueString().length() >= minClasspathLength);
+
+		// When
+		resumeAndExit(thread);
+
+		// Then
+		if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+			// On windows, temp file deletion may fail
+			assertFalse(tempFile.exists());
+		}
+	}
+
+	/*
+	 * On Windows, for JVM < 9, the CLASSPATH env variable is used if classpath is too long
+	 */
+	public void testVeryLongClasspathWithEnvironmentVariable() throws Exception {
+		assumeThat(Platform.getOS(), equalTo(Platform.OS_WIN32));
+
+		// Given
+		javaProject = createJavaProjectClone("testVeryLongClasspath", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_1_6_EE_NAME, true);
+		launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+		assumeFalse(isArgumentFileSupported(launchConfiguration));
+		int minClasspathLength = 300000;
+		setLongClasspath(javaProject, minClasspathLength);
+		waitForBuild();
+
+		// When
+		thread = launchAndSuspend(launchConfiguration);
+
+		// Then
+		assertTrue(doEval(thread, "System.getProperty(\"java.class.path\")").getValueString().length() >= minClasspathLength);
+		resumeAndExit(thread);
+	}
+
+	private Optional<File> getTempFile(ILaunch launch) {
+		IProcess process = launch.getProcesses()[0];
+		String tempFile = process.getAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES);
+		if (tempFile == null) {
+			return Optional.empty();
+		}
+		return Optional.of(new File(tempFile));
+	}
+
+	private boolean isArgumentFileSupported(ILaunchConfiguration launchConfiguration) throws CoreException {
+		IVMInstall vmInstall = JavaRuntime.computeVMInstall(launchConfiguration);
+		if (vmInstall instanceof AbstractVMInstall) {
+			AbstractVMInstall install = (AbstractVMInstall) vmInstall;
+			String vmver = install.getJavaVersion();
+			if (JavaCore.compareJavaVersions(vmver, JavaCore.VERSION_9) >= 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private ILaunchConfiguration enableClasspathOnlyJar(ILaunchConfiguration launchConfiguration) throws CoreException {
+		ILaunchConfigurationWorkingCopy configurationWorkingCopy = launchConfiguration.getWorkingCopy();
+		configurationWorkingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, true);
+		return configurationWorkingCopy.doSave();
+	}
+
+	private ILaunchConfiguration createLaunchConfigurationStopInMain(IJavaProject javaProject, String mainTypeName) throws Exception, CoreException {
+		ILaunchConfiguration launchConfiguration;
+		launchConfiguration = createLaunchConfiguration(javaProject, mainTypeName);
+		ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
+		wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
+		launchConfiguration = wc.doSave();
+		return launchConfiguration;
+	}
+
+	private void setLongClasspath(IJavaProject javaProject, int minClassPathLength) throws Exception {
+		StringBuilder sb = new StringBuilder();
+		List<IClasspathEntry> classpathEntries = new ArrayList<>();
+		int i = 0;
+		while (sb.length() < minClassPathLength) {
+			String jarName = "library" + i + ".jar";
+			IPath targetPath = javaProject.getPath().append("lib/" + jarName);
+			javaProject.getProject().getFile("lib/classpath.jar").copy(targetPath, IResource.FORCE, new NullProgressMonitor());
+			IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+			classpathEntries.add(JavaCore.newLibraryEntry(targetPath, null, null, null, new IClasspathAttribute[] {
+					moduleClasspathAttribute }, false));
+			if (i != 0) {
+				sb.append(File.pathSeparator);
+			}
+			sb.append(javaProject.getProject().getFile("lib/" + jarName).getLocation().toString());
+			i++;
+		}
+		classpathEntries.add(JavaCore.newLibraryEntry(javaProject.getPath().append("lib/classpath.jar"), null, null));
+		sb.append(File.pathSeparator);
+		sb.append(javaProject.getProject().getFile("lib/classpath.jar").getLocation().toString());
+		classpathEntries.addAll(Arrays.asList(javaProject.getRawClasspath()));
+		javaProject.setRawClasspath(classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), null);
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java
new file mode 100644
index 0000000..b22ddca
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.LaunchingPlugin;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.BuildPathSupport;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.JavaRuntime;
+import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
+
+/**
+ * Test long module-path. OSs have limits in term of command line length or argument length. We use an argument file when module-path is too long.
+ *
+ */
+public class LongModulePathTests extends AbstractDebugTest {
+	private static final IPath CLASSPATH_PROJECT_CONTENT_PATH = new Path("testresources/classpathModuleProject");
+	private static final String MAIN_TYPE_NAME = "test.classpath.Main";
+	private IJavaProject javaProject;
+	private IJavaThread thread;
+	private ILaunchConfiguration launchConfiguration;
+
+	public LongModulePathTests(String name) {
+		super(name);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		try {
+			if (thread != null) {
+				terminateAndRemove(thread);
+			}
+			if (javaProject != null) {
+				javaProject.getProject().delete(true, true, null);
+			}
+			if (launchConfiguration != null) {
+				launchConfiguration.delete();
+			}
+		} catch (CoreException ce) {
+			// ignore
+		} finally {
+			super.tearDown();
+		}
+	}
+
+	/*
+	 * When JVM > 9, an argument file for the modulepath is created when modulepath is too long
+	 */
+	public void testVeryLongModulepathWithArgumentFile() throws Exception {
+		// Given
+		javaProject = createJavaProjectClone("testVeryLongModulePath", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_9_EE_NAME, true);
+		useComplianceFromExecutionEnvironment(javaProject);
+		useModuleForJREContainer(javaProject);
+		launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+		int minModulePathLength = 300000;
+		setLongModulepath(javaProject, minModulePathLength);
+		waitForBuild();
+
+		// When
+		thread = launchAndSuspend(launchConfiguration);
+
+		// Then
+		File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+		assertTrue(tempFile.exists());
+		assertTrue(tempFile.getName().endsWith(".txt"));
+		assertTrue(doEval(thread, "System.getProperty(\"jdk.module.path\")").getValueString().length() >= minModulePathLength);
+
+		// When
+		resumeAndExit(thread);
+
+		// Then
+		if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+			// On windows, temp file deletion may fail
+			assertFalse(tempFile.exists());
+		}
+	}
+
+	private ILaunchConfiguration createLaunchConfigurationStopInMain(IJavaProject javaProject, String mainTypeName) throws Exception, CoreException {
+		ILaunchConfiguration launchConfiguration;
+		launchConfiguration = createLaunchConfiguration(javaProject, mainTypeName);
+		ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
+		wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
+		launchConfiguration = wc.doSave();
+		return launchConfiguration;
+	}
+
+	private void setLongModulepath(IJavaProject javaProject, int minModulePathLength) throws Exception {
+		StringBuilder sb = new StringBuilder();
+		List<IClasspathEntry> classpathEntries = new ArrayList<>();
+		int i = 0;
+		while (sb.length() < minModulePathLength) {
+			String jarName = "library" + i + ".jar";
+			IPath targetPath = javaProject.getPath().append("lib/" + jarName);
+			javaProject.getProject().getFile("lib/classpath.jar").copy(targetPath, IResource.FORCE, new NullProgressMonitor());
+			IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+			classpathEntries.add(JavaCore.newLibraryEntry(targetPath, null, null, null, new IClasspathAttribute[] {
+					moduleClasspathAttribute }, false));
+			if (i != 0) {
+				sb.append(File.pathSeparator);
+			}
+			sb.append(javaProject.getProject().getFile("lib/" + jarName).getLocation().toString());
+			i++;
+		}
+		IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+		classpathEntries.add(JavaCore.newLibraryEntry(javaProject.getPath().append("lib/classpath.jar"), null, null, null, new IClasspathAttribute[] {
+				moduleClasspathAttribute }, false));
+		sb.append(File.pathSeparator);
+		sb.append(javaProject.getProject().getFile("lib/classpath.jar").getLocation().toString());
+		classpathEntries.addAll(Arrays.asList(javaProject.getRawClasspath()));
+		javaProject.setRawClasspath(classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), null);
+	}
+
+	private void useComplianceFromExecutionEnvironment(IJavaProject javaProject) throws JavaModelException {
+		IExecutionEnvironment executionEnvironment = getExecutionEnvironment(javaProject);
+		Map<String, String> eeOptions = BuildPathSupport.getEEOptions(executionEnvironment);
+		eeOptions.forEach((optionName, optionValue) -> javaProject.setOption(optionName, optionValue));
+	}
+
+	private IExecutionEnvironment getExecutionEnvironment(IJavaProject javaProject) throws JavaModelException {
+		IClasspathEntry[] entries = javaProject.getRawClasspath();
+		for (int i = 0; i < entries.length; i++) {
+			IClasspathEntry entry = entries[i];
+			if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+				String eeId = JavaRuntime.getExecutionEnvironmentId(entry.getPath());
+				if (eeId != null) {
+					return JavaRuntime.getExecutionEnvironmentsManager().getEnvironment(eeId);
+				}
+			}
+		}
+		return null;
+	}
+
+	private void useModuleForJREContainer(IJavaProject javaProject) throws JavaModelException {
+		IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
+		for (int i = 0; i < rawClasspath.length; i++) {
+			IClasspathEntry classpathEntry = rawClasspath[i];
+			if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+				IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+				classpathEntry = JavaCore.newContainerEntry(classpathEntry.getPath(), classpathEntry.getAccessRules(), new IClasspathAttribute[] {
+						moduleClasspathAttribute }, classpathEntry.isExported());
+				rawClasspath[i] = classpathEntry;
+			}
+		}
+		javaProject.setRawClasspath(rawClasspath, null);
+	}
+
+	private Optional<File> getTempFile(ILaunch launch) {
+		IProcess process = launch.getProcesses()[0];
+		String tempFile = process.getAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES);
+		if (tempFile == null) {
+			return Optional.empty();
+		}
+		return Optional.of(new File(tempFile));
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml
index 7d4b03a..9cdacd2 100644
--- a/org.eclipse.jdt.debug.ui/plugin.xml
+++ b/org.eclipse.jdt.debug.ui/plugin.xml
@@ -2495,6 +2495,12 @@
             class="org.eclipse.jdt.internal.debug.ui.EvaluationStackFrameContextStatusHandler"
             id="org.eclipse.jdt.debug.ui.statusHandler.evaluationStackFrameContextStatusHandler"
             plugin="org.eclipse.jdt.debug"/>
+      <statusHandler
+            class="org.eclipse.jdt.internal.debug.ui.launcher.ClasspathTooLongStatusHandler"
+            code="125"
+            id="org.eclipse.jdt.debug.ui.statusHandler.classpathTooLongStatusHandler"
+            plugin="org.eclipse.jdt.launching">
+      </statusHandler>
    </extension>
    <extension
          point="org.eclipse.debug.core.sourceLocators">
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
index ff7f8f5..6fadda6 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
@@ -96,6 +96,7 @@
 	protected ILaunchConfiguration fLaunchConfiguration;
 
 	private Button fExcludeTestCodeButton;
+	private Button fUseClasspathOnlyJarButton;
 
 	/**
 	 * Constructor
@@ -155,6 +156,14 @@
 				updateLaunchConfigurationDialog();
 			}
 		});
+		fUseClasspathOnlyJarButton = SWTFactory.createCheckButton(comp, LauncherMessages.VMArgumentsBlock_1, null, false, 1);
+		fUseClasspathOnlyJarButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				setDirty(true);
+				updateLaunchConfigurationDialog();
+			}
+		});
 	}
 
 	/**
@@ -228,6 +237,7 @@
 		fClasspathViewer.getTreeViewer().expandToLevel(2);
 		try {
 			fExcludeTestCodeButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false));
+			fUseClasspathOnlyJarButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false));
 		} catch (CoreException e) {
 		}
 	}
@@ -321,6 +331,7 @@
 			catch (CoreException e) {
 				JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
 			}
+			configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, fUseClasspathOnlyJarButton.getSelection());
 		}
 	}
 
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
index 33f200f..fa87bd3 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
@@ -220,6 +220,11 @@
 		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
 		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
 
+		// we don't want to use classpath only jars for modular projects
+		if (JavaRuntime.isModularConfiguration(configuration)) {
+			configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false);
+		}
+
 		// Handle any attributes in the VM-specific area
 		ILaunchConfigurationTab dynamicTab = getDynamicTab();
 		if (dynamicTab == null) {
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java
new file mode 100644
index 0000000..a755cdc
--- /dev/null
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Cedric Chabanois (cchabanois@gmail.com) - Launching command line exceeds the process creation command limit on *nix - https://bugs.eclipse.org/bugs/show_bug.cgi?id=385738
+ *******************************************************************************/
+package org.eclipse.jdt.internal.debug.ui.launcher;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.IStatusHandler;
+import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+
+/**
+ * Handle status when classpath is too long and that this could not be solved automatically.
+ *
+ * We ask the user if he wants to use classpath-only jar. We need confirmation from the user because it can have side effects
+ * (System.getProperty("java.class.path") will return a classpath with only one jar).
+ */
+public class ClasspathTooLongStatusHandler implements IStatusHandler {
+
+	@Override
+	public Object handleStatus(IStatus status, Object source) {
+		ILaunch launch = (ILaunch) source;
+		ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
+		boolean enableClasspathOnlyJar = askEnableClasspathOnlyJar();
+		if (enableClasspathOnlyJar && launchConfiguration != null) {
+			try {
+				ILaunchConfigurationWorkingCopy configurationWorkingCopy = launchConfiguration.getWorkingCopy();
+				configurationWorkingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, true);
+				configurationWorkingCopy.doSave();
+			} catch (CoreException e) {
+				JDIDebugUIPlugin.log(e);
+			}
+		}
+		return Boolean.valueOf(enableClasspathOnlyJar);
+	}
+
+	private boolean askEnableClasspathOnlyJar() {
+		final boolean[] result = new boolean[1];
+		JDIDebugUIPlugin.getStandardDisplay().syncExec(new Runnable() {
+			@Override
+			public void run() {
+				String title = LauncherMessages.ClasspathTooLongStatusHandler_0;
+				String message = LauncherMessages.ClasspathTooLongStatusHandler_1;
+				result[0] = (MessageDialog.openQuestion(JDIDebugUIPlugin.getActiveWorkbenchShell(), title, message));
+			}
+		});
+		return result[0];
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
index b2bfa90..10d9c95 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
@@ -48,6 +48,8 @@
 
 	public static String VMArgumentsBlock_0;
 
+	public static String VMArgumentsBlock_1;
+
 	public static String VMArgumentsBlock_VM_Arguments;
 	public static String VMArgumentsBlock_4;
 
@@ -180,6 +182,10 @@
 
 	public static String AppletSelectionDialog_Searching____1;
 
+	public static String ClasspathTooLongStatusHandler_0;
+
+	public static String ClasspathTooLongStatusHandler_1;
+
 	public static String ExecutionEnvironmentSelector_0;
 	public static String ExecutionEnvironmentSelector_1;
 
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
index 4cd96bd..faf071e 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
@@ -25,6 +25,7 @@
 RuntimeClasspathAdvancedDialog_7=Va&riables...
 
 VMArgumentsBlock_0=Use the -&XstartOnFirstThread argument when launching with SWT
+VMArgumentsBlock_1=Use temporary jar to specify classpath (to avoid classpath length limitations)
 VMArgumentsBlock_VM_Arguments=VM Arguments
 
 JavaConnectTab__Allow_termination_of_remote_VM_6=&Allow termination of remote VM
@@ -177,6 +178,8 @@
 AbstractJavaMainTab_2=&Search...
 AbstractJavaMainTab_4=Project Selection
 AbstractJavaMainTab_3=Select a project to constrain your search.
+ClasspathTooLongStatusHandler_0=Java Application
+ClasspathTooLongStatusHandler_1=Cannot start the Java Virtual Machine because classpath is too long. Do you want to enable classpath-only jar for this launch configuration ?
 ExecutionEnvironmentSelector_0=Select Execution Environment
 ExecutionEnvironmentSelector_1=Choose an environment (? = any character, * = any string):
 ProjectClasspathArugumentSelector_0=Select Java Project
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java
new file mode 100644
index 0000000..a4a1be6
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java
@@ -0,0 +1,481 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Cedric Chabanois (cchabanois@gmail.com) - Launching command line exceeds the process creation command limit on *nix - https://bugs.eclipse.org/bugs/show_bug.cgi?id=385738
+ *     IBM Corporation - Launching command line exceeds the process creation command limit on Windows - https://bugs.eclipse.org/bugs/show_bug.cgi?id=327193
+ *******************************************************************************/
+package org.eclipse.jdt.internal.launching;
+
+import static org.eclipse.jdt.internal.launching.LaunchingPlugin.LAUNCH_TEMP_FILE_PREFIX;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.URIUtil;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.IStatusHandler;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.IVMInstall2;
+
+/**
+ * Shorten the classpath/modulepath if necessary.
+ *
+ * Depending on the java version, os and launch configuration, the classpath argument will be replaced by an argument file, a classpath-only jar or
+ * env variable. The modulepath is replaced by an argument file if necessary.
+ *
+ */
+public class ClasspathShortener {
+	private static final String CLASSPATH_ENV_VAR_PREFIX = "CLASSPATH="; //$NON-NLS-1$
+	public static final int ARG_MAX_LINUX = 2097152;
+	public static final int ARG_MAX_WINDOWS = 32767;
+	public static final int ARG_MAX_MACOS = 262144;
+	public static final int MAX_ARG_STRLEN_LINUX = 131072;
+	private final String os;
+	private final String javaVersion;
+	private final ILaunch launch;
+	private final List<String> cmdLine;
+	private int lastJavaArgumentIndex;
+	private String[] envp;
+	private File processTempFilesDir;
+	private final List<File> processTempFiles = new ArrayList<>();
+
+	/**
+	 *
+	 * @param vmInstall
+	 *            the vm installation
+	 * @param launch
+	 *            the launch
+	 * @param cmdLine
+	 *            the command line (java executable + VM arguments + program arguments)
+	 * @param lastJavaArgumentIndex
+	 *            the index of the last java argument in cmdLine (next arguments if any are program arguments)
+	 * @param workingDir
+	 *            the working dir to use for the launched VM or null
+	 * @param envp
+	 *            array of strings, each element of which has environment variable settings in the format name=value, or null if the subprocess should
+	 *            inherit the environment of the current process.
+	 */
+	public ClasspathShortener(IVMInstall vmInstall, ILaunch launch, String[] cmdLine, int lastJavaArgumentIndex, File workingDir, String[] envp) {
+		this(Platform.getOS(), getJavaVersion(vmInstall), launch, cmdLine, lastJavaArgumentIndex, workingDir, envp);
+	}
+
+	protected ClasspathShortener(String os, String javaVersion, ILaunch launch, String[] cmdLine, int lastJavaArgumentIndex, File workingDir, String[] envp) {
+		Assert.isNotNull(os);
+		Assert.isNotNull(javaVersion);
+		Assert.isNotNull(launch);
+		Assert.isNotNull(cmdLine);
+		this.os = os;
+		this.javaVersion = javaVersion;
+		this.launch = launch;
+		this.cmdLine = new ArrayList<>(Arrays.asList(cmdLine));
+		this.lastJavaArgumentIndex = lastJavaArgumentIndex;
+		this.envp = envp == null ? null : Arrays.copyOf(envp, envp.length);
+		this.processTempFilesDir = workingDir != null ? workingDir : Paths.get(".").toAbsolutePath().normalize().toFile(); //$NON-NLS-1$
+	}
+
+	/**
+	 * The directory to use to create temp files needed when shortening the classpath. By default, the working directory is used
+	 *
+	 * The java.io.tmpdir should not be used on MacOs (does not work for classpath-only jars)
+	 *
+	 * @param processTempFilesDir
+	 */
+	public void setProcessTempFilesDir(File processTempFilesDir) {
+		this.processTempFilesDir = processTempFilesDir;
+	}
+
+	public File getProcessTempFilesDir() {
+		return processTempFilesDir;
+	}
+
+	/**
+	 * Get the new envp. May have been modified to shorten the classpath
+	 *
+	 * @return environment variables in the format name=value or null
+	 */
+	public String[] getEnvp() {
+		return envp;
+	}
+
+	/**
+	 * Get the new command line. Modified if command line or classpath argument were too long
+	 *
+	 * @return the command line (java executable + VM arguments + program arguments)
+	 */
+	public String[] getCmdLine() {
+		return cmdLine.toArray(new String[cmdLine.size()]);
+	}
+
+	/**
+	 * The files that were created while shortening the path. They can be deleted once the process is terminated
+	 *
+	 * @return created files
+	 */
+	public List<File> getProcessTempFiles() {
+		return new ArrayList<>(processTempFiles);
+	}
+
+	/**
+	 * Shorten the command line if necessary. Each OS has different limits for command line length or command line argument length. And depending on
+	 * the OS, JVM version and launch configuration, we shorten the classpath using an argument file, a classpath-only jar or env variable.
+	 *
+	 * If we need to use a classpath-only jar to shorten the classpath, we ask confirmation from the user because it can have side effects
+	 * (System.getProperty("java.class.path") will return a classpath with only one jar). If
+	 * {@link IJavaLaunchConfigurationConstants#ATTR_USE_CLASSPATH_ONLY_JAR} is set, a classpath-only jar is used (without asking confirmation).
+	 *
+	 * @return true if command line has been shortened or false if it was not necessary or not possible. Use {@link #getCmdLine()} and
+	 *         {@link #getEnvp()} to get the new command line/environment.
+	 */
+	public boolean shortenCommandLineIfNecessary() {
+		// '|' used on purpose (not short-circuiting)
+		return shortenClasspathIfNecessary() | shortenModulePathIfNecessary();
+	}
+
+	private int getClasspathArgumentIndex() {
+		for (int i = 0; i <= lastJavaArgumentIndex; i++) {
+			String element = cmdLine.get(i);
+			if ("-cp".equals(element) || "-classpath".equals(element) || "--class-path".equals(element)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+				return i + 1;
+			}
+		}
+		return -1;
+	}
+
+	private int getModulepathArgumentIndex() {
+		for (int i = 0; i <= lastJavaArgumentIndex; i++) {
+			String element = cmdLine.get(i);
+			if ("-p".equals(element) || "--module-path".equals(element)) { //$NON-NLS-1$ //$NON-NLS-2$
+				return i + 1;
+			}
+		}
+		return -1;
+	}
+
+	private boolean shortenModulePathIfNecessary() {
+		int modulePathArgumentIndex = getModulepathArgumentIndex();
+		if (modulePathArgumentIndex == -1) {
+			return false;
+		}
+		try {
+			String modulePath = cmdLine.get(modulePathArgumentIndex);
+			if (getCommandLineLength() <= getMaxCommandLineLength() && modulePath.length() <= getMaxArgLength()) {
+				return false;
+			}
+			if (isArgumentFileSupported()) {
+				shortenModulePathUsingModulePathArgumentFile(modulePathArgumentIndex);
+				return true;
+			}
+		} catch (CoreException e) {
+			LaunchingPlugin.log(e.getStatus());
+		}
+		return false;
+	}
+
+	private boolean shortenClasspathIfNecessary() {
+		int classpathArgumentIndex = getClasspathArgumentIndex();
+		if (classpathArgumentIndex == -1) {
+			return false;
+		}
+		try {
+			boolean forceUseClasspathOnlyJar = getLaunchConfigurationUseClasspathOnlyJarAttribute();
+			if (forceUseClasspathOnlyJar) {
+				shortenClasspathUsingClasspathOnlyJar(classpathArgumentIndex);
+				return true;
+			}
+			String classpath = cmdLine.get(classpathArgumentIndex);
+			if (getCommandLineLength() <= getMaxCommandLineLength() && classpath.length() <= getMaxArgLength()) {
+				return false;
+			}
+			if (isArgumentFileSupported()) {
+				shortenClasspathUsingClasspathArgumentFile(classpathArgumentIndex);
+				return true;
+			}
+			if (os.equals(Platform.OS_WIN32)) {
+				shortenClasspathUsingClasspathEnvVariable(classpathArgumentIndex);
+				return true;
+			} else if (handleClasspathTooLongStatus()) {
+				shortenClasspathUsingClasspathOnlyJar(classpathArgumentIndex);
+				return true;
+			}
+		} catch (CoreException e) {
+			LaunchingPlugin.log(e.getStatus());
+		}
+		return false;
+	}
+
+	protected boolean getLaunchConfigurationUseClasspathOnlyJarAttribute() throws CoreException {
+		ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
+		if (launchConfiguration == null) {
+			return false;
+		}
+		return launchConfiguration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false);
+	}
+
+	public static String getJavaVersion(IVMInstall vmInstall) {
+		if (vmInstall instanceof IVMInstall2) {
+			IVMInstall2 install = (IVMInstall2) vmInstall;
+			return install.getJavaVersion();
+		}
+		return null;
+	}
+
+	private boolean isArgumentFileSupported() {
+		return JavaCore.compareJavaVersions(javaVersion, JavaCore.VERSION_9) >= 0;
+	}
+
+	private int getCommandLineLength() {
+		return cmdLine.stream().map(argument -> argument.length() + 1).reduce((a, b) -> a + b).get();
+	}
+
+	private int getEnvironmentLength() {
+		if (envp == null) {
+			return 0;
+		}
+		return Arrays.stream(envp).map(element -> element.length() + 1).reduce((a, b) -> a + b).orElse(0);
+	}
+
+	protected int getMaxCommandLineLength() {
+		// for Posix systems, ARG_MAX is the maximum length of argument to the exec functions including environment data.
+		// POSIX suggests to subtract 2048 additionally so that the process may safely modify its environment.
+		// see https://www.in-ulm.de/~mascheck/various/argmax/
+		switch (os) {
+			case Platform.OS_LINUX:
+				// ARG_MAX will be 1/4 of the stack size. 2097152 by default
+				return ARG_MAX_LINUX - getEnvironmentLength() - 2048;
+			case Platform.OS_MACOSX:
+				// on MacOs, ARG_MAX is 262144
+				return ARG_MAX_MACOS - getEnvironmentLength() - 2048;
+			case Platform.OS_WIN32:
+				// On Windows, the maximum length of the command line is 32,768 characters, including the Unicode terminating null character.
+				// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
+				return ARG_MAX_WINDOWS - 2048;
+			default:
+				return Integer.MAX_VALUE;
+		}
+	}
+
+	protected int getMaxArgLength() {
+		if (os.equals(Platform.OS_LINUX)) {
+			// On Linux, MAX_ARG_STRLEN (kernel >= 2.6.23) is the maximum length of a command line argument (or environment variable). Its value
+			// cannot be changed without recompiling the kernel.
+			return MAX_ARG_STRLEN_LINUX - 2048;
+		}
+		return Integer.MAX_VALUE;
+	}
+
+	private void shortenClasspathUsingClasspathArgumentFile(int classpathArgumentIndex) throws CoreException {
+		String classpath = cmdLine.get(classpathArgumentIndex);
+		File file = createClassPathArgumentFile(classpath);
+		removeCmdLineArgs(classpathArgumentIndex - 1, 2);
+		addCmdLineArgs(classpathArgumentIndex - 1, '@' + file.getAbsolutePath());
+		addProcessTempFile(file);
+	}
+
+	private void shortenModulePathUsingModulePathArgumentFile(int modulePathArgumentIndex) throws CoreException {
+		String modulePath = cmdLine.get(modulePathArgumentIndex);
+		File file = createModulePathArgumentFile(modulePath);
+		removeCmdLineArgs(modulePathArgumentIndex - 1, 2);
+		addCmdLineArgs(modulePathArgumentIndex - 1, '@' + file.getAbsolutePath());
+		addProcessTempFile(file);
+	}
+
+	private void shortenClasspathUsingClasspathOnlyJar(int classpathArgumentIndex) throws CoreException {
+		String classpath = cmdLine.get(classpathArgumentIndex);
+		File classpathOnlyJar = createClasspathOnlyJar(classpath);
+		removeCmdLineArgs(classpathArgumentIndex, 1);
+		addCmdLineArgs(classpathArgumentIndex, classpathOnlyJar.getAbsolutePath());
+		addProcessTempFile(classpathOnlyJar);
+	}
+
+	protected void addProcessTempFile(File file) {
+		processTempFiles.add(file);
+	}
+
+	protected boolean handleClasspathTooLongStatus() throws CoreException {
+		IStatus status = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IJavaLaunchConfigurationConstants.ERR_CLASSPATH_TOO_LONG, "", null); //$NON-NLS-1$
+		IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
+		if (handler == null) {
+			return false;
+		}
+		Object result = handler.handleStatus(status, launch);
+		if (!(result instanceof Boolean)) {
+			return false;
+		}
+		return (boolean) result;
+	}
+
+	private File createClasspathOnlyJar(String classpath) throws CoreException {
+		try {
+			String timeStamp = getLaunchTimeStamp();
+			File jarFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+					+ "%s-classpathOnly-%s.jar", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+			URI workingDirUri = processTempFilesDir.toURI();
+			StringBuilder manifestClasspath = new StringBuilder();
+			String[] classpathArray = getClasspathAsArray(classpath);
+			for (int i = 0; i < classpathArray.length; i++) {
+				if (i != 0) {
+					manifestClasspath.append(' ');
+				}
+				File file = new File(classpathArray[i]);
+				String relativePath = URIUtil.makeRelative(file.toURI(), workingDirUri).toString();
+				manifestClasspath.append(relativePath);
+			}
+			Manifest manifest = new Manifest();
+			manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); //$NON-NLS-1$
+			manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, manifestClasspath.toString());
+			try (JarOutputStream target = new JarOutputStream(new FileOutputStream(jarFile), manifest)) {
+			}
+			return jarFile;
+		} catch (IOException e) {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create classpath only jar", e)); // $NON-NLS-1$ //$NON-NLS-1$
+		}
+	}
+
+	private String[] getClasspathAsArray(String classpath) {
+		return classpath.split("" + getPathSeparatorChar()); //$NON-NLS-1$
+	}
+
+	protected char getPathSeparatorChar() {
+		char separator = ':';
+		if (os.equals(Platform.OS_WIN32)) {
+			separator = ';';
+		}
+		return separator;
+	}
+
+	protected String getLaunchConfigurationName() {
+		return launch.getLaunchConfiguration().getName();
+	}
+
+	private File createClassPathArgumentFile(String classpath) throws CoreException {
+		try {
+			String timeStamp = getLaunchTimeStamp();
+			File classPathFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+					+ "%s-classpath-arg-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+			byte[] bytes = ("-classpath " + classpath).getBytes(StandardCharsets.UTF_8); //$NON-NLS-1$
+
+			Files.write(classPathFile.toPath(), bytes);
+			return classPathFile;
+		} catch (IOException e) {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create classpath argument file", e)); //$NON-NLS-1$
+		}
+	}
+
+	private File createModulePathArgumentFile(String modulePath) throws CoreException {
+		try {
+			String timeStamp = getLaunchTimeStamp();
+			File modulePathFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+					+ "%s-module-path-arg-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+			byte[] bytes = ("--module-path " + modulePath).getBytes(StandardCharsets.UTF_8); //$NON-NLS-1$
+
+			Files.write(modulePathFile.toPath(), bytes);
+			return modulePathFile;
+		} catch (IOException e) {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create module-path argument file", e)); //$NON-NLS-1$
+		}
+	}
+
+	protected String getLaunchTimeStamp() {
+		String timeStamp = launch.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP);
+		if (timeStamp == null) {
+			timeStamp = Long.toString(System.currentTimeMillis());
+		}
+		return timeStamp;
+	}
+
+	private String[] getEnvpFromNativeEnvironment() {
+		Map<String, String> nativeEnvironment = getNativeEnvironment();
+		String[] envp = new String[nativeEnvironment.size()];
+		int idx = 0;
+		for (Entry<String, String> entry : nativeEnvironment.entrySet()) {
+			String value = entry.getValue();
+			if (value == null) {
+				value = ""; //$NON-NLS-1$
+			}
+			String key = entry.getKey();
+			envp[idx] = key + '=' + value;
+			idx++;
+		}
+		return envp;
+	}
+
+	protected Map<String, String> getNativeEnvironment() {
+		return DebugPlugin.getDefault().getLaunchManager().getNativeEnvironment();
+	}
+
+	private void shortenClasspathUsingClasspathEnvVariable(int classpathArgumentIndex) {
+		String classpath = cmdLine.get(classpathArgumentIndex);
+		if (envp == null) {
+			envp = getEnvpFromNativeEnvironment();
+		}
+		String classpathEnvVar = CLASSPATH_ENV_VAR_PREFIX + classpath;
+		int index = getEnvClasspathIndex(envp);
+		if (index < 0) {
+			envp = Arrays.copyOf(envp, envp.length + 1);
+			envp[envp.length - 1] = classpathEnvVar;
+		} else {
+			envp[index] = classpathEnvVar;
+		}
+		removeCmdLineArgs(classpathArgumentIndex - 1, 2);
+	}
+
+	private void removeCmdLineArgs(int index, int length) {
+		for (int i = 0; i < length; i++) {
+			cmdLine.remove(index);
+			lastJavaArgumentIndex--;
+		}
+	}
+
+	private void addCmdLineArgs(int index, String... newArgs) {
+		cmdLine.addAll(index, Arrays.asList(newArgs));
+		lastJavaArgumentIndex += newArgs.length;
+	}
+
+	/**
+	 * Returns the index in the given array for the CLASSPATH variable
+	 *
+	 * @param env
+	 *            the environment array or <code>null</code>
+	 * @return -1 or the index of the CLASSPATH variable
+	 */
+	private int getEnvClasspathIndex(String[] env) {
+		if (env != null) {
+			for (int i = 0; i < env.length; i++) {
+				if (env[i].regionMatches(true, 0, CLASSPATH_ENV_VAR_PREFIX, 0, 10)) {
+					return i;
+				}
+			}
+		}
+		return -1;
+	}
+
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
index bdc2ee4..9c20eb8 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
@@ -22,6 +22,7 @@
 import java.io.OutputStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -104,6 +105,17 @@
 	public static final String DEBUG_FLAG = "org.eclipse.jdt.launching/debug"; //$NON-NLS-1$
 
 	/**
+	 * list of temp files for the launch (separated by the path separator char). Files must start with {@link #LAUNCH_TEMP_FILE_PREFIX} and will be
+	 * deleted once the process is terminated
+	 */
+	public static final String ATTR_LAUNCH_TEMP_FILES = "tempFiles"; //$NON-NLS-1$
+
+	/**
+	 * prefix for temp files
+	 */
+	public static final String LAUNCH_TEMP_FILE_PREFIX = ".temp-"; //$NON-NLS-1$
+
+	/**
 	 * The {@link DebugTrace} object to print to OSGi tracing
 	 * @since 3.8
 	 */
@@ -1178,11 +1190,31 @@
 				Object source = event.getSource();
 				if (source instanceof IDebugTarget || source instanceof IProcess) {
 					ArchiveSourceLocation.closeArchives();
+					IProcess process;
+					if (source instanceof IProcess) {
+						process = (IProcess) source;
+					} else {
+						process = ((IDebugTarget) source).getProcess();
+					}
+					deleteProcessTempFiles(process);
 				}
 			}
 		}
 	}
 
+	private void deleteProcessTempFiles(IProcess process) {
+		String tempFiles = process.getAttribute(ATTR_LAUNCH_TEMP_FILES);
+		if (tempFiles == null) {
+			return;
+		}
+		// we only delete files starting with LAUNCH_TEMP_FILE_PREFIX²
+		Arrays.stream(tempFiles.split(File.pathSeparator)).map(path -> new File(path)).filter(file -> isValidProcessTempFile(file)).forEach(file -> file.delete());
+	}
+
+	private boolean isValidProcessTempFile(File file) {
+		return file.getName().startsWith(LAUNCH_TEMP_FILE_PREFIX);
+	}
+
 	/**
 	 * Returns a shared XML parser.
 	 *
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
index fb9f643..6b08810 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
@@ -15,6 +15,7 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -83,9 +84,7 @@
 			combinedPath[offset] = classPath[i];
 			offset++;
 		}
-		int cpidx = -1;
 		if (combinedPath.length > 0) {
-			cpidx = arguments.size();
 			arguments.add("-classpath"); //$NON-NLS-1$
 			arguments.add(convertClassPath(combinedPath));
 		}
@@ -94,12 +93,7 @@
 		String[] programArgs= config.getProgramArguments();
 
 		String[] envp = prependJREPath(config.getEnvironment());
-		String[] newenvp = checkClasspath(arguments, classPath, envp);
-		if(newenvp != null) {
-			envp = newenvp;
-			arguments.remove(cpidx);
-			arguments.remove(cpidx);
-		}
+		int lastVMArgumentIndex = arguments.size() - 1;
 		addArguments(programArgs, arguments);
 
 		String[] cmdLine= new String[arguments.size()];
@@ -109,12 +103,17 @@
 		if (monitor.isCanceled()) {
 			return;
 		}
+		File workingDir = getWorkingDir(config);
+		ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+		if (classpathShortener.shortenCommandLineIfNecessary()) {
+			cmdLine = classpathShortener.getCmdLine();
+			envp = classpathShortener.getEnvp();
+		}
 
 		subMonitor.worked(1);
 		subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
 
 		Process p= null;
-		File workingDir = getWorkingDir(config);
 		String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
 		if(newCmdLine != null) {
 			cmdLine = newCmdLine;
@@ -138,6 +137,10 @@
 		if(workingDir != null) {
 			process.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, workingDir.getAbsolutePath());
 		}
+		if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+			String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+			process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+		}
 		subMonitor.worked(1);
 	}
 }
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
index f652aa0..caa4d05 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
@@ -21,6 +21,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -217,9 +218,7 @@
 		}
 
 		String[] cp= config.getClassPath();
-		int cpidx = -1;
 		if (cp.length > 0) {
-			cpidx = arguments.size();
 			arguments.add("-classpath"); //$NON-NLS-1$
 			arguments.add(convertClassPath(cp));
 		}
@@ -239,7 +238,7 @@
 		} else {
 			arguments.add(config.getClassToLaunch());
 		}
-
+		int lastVMArgumentIndex = arguments.size() - 1;
 		/*
 		 * String[] cp= config.getClassPath(); int cpidx = -1; if (cp.length > 0) { cpidx = arguments.size(); arguments.add("-classpath");
 		 * //$NON-NLS-1$ arguments.add(convertClassPath(cp)); }
@@ -253,13 +252,6 @@
 		//format: <jdk path>/jre/bin
 		String[] envp = prependJREPath(config.getEnvironment(), new Path(program));
 
-		String[] newenvp = checkClasspath(arguments, cp, envp);
-		if(newenvp != null) {
-			envp = newenvp;
-			arguments.remove(cpidx);
-			arguments.remove(cpidx);
-		}
-
 		String[] cmdLine= new String[arguments.size()];
 		arguments.toArray(cmdLine);
 
@@ -267,6 +259,12 @@
 		if (monitor.isCanceled()) {
 			return;
 		}
+		File workingDir = getWorkingDir(config);
+		ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+		if (classpathShortener.shortenCommandLineIfNecessary()) {
+			cmdLine = classpathShortener.getCmdLine();
+			envp = classpathShortener.getEnvp();
+		}
 
 		subMonitor.worked(1);
 		subMonitor.subTask(LaunchingMessages.StandardVMDebugger_Starting_virtual_machine____4);
@@ -288,7 +286,6 @@
 
 				connector.startListening(map);
 
-				File workingDir = getWorkingDir(config);
 				String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
 				if(newCmdLine != null) {
 					cmdLine = newCmdLine;
@@ -323,6 +320,10 @@
 					}
 					process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
 				}
+				if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+					String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+					process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+				}
 				subMonitor.worked(1);
 				subMonitor.subTask(LaunchingMessages.StandardVMDebugger_Establishing_debug_connection____5);
 				int retryCount = 0;
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
index fffbb21..47060d2 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
@@ -20,6 +20,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -380,9 +381,7 @@
 			arguments.add(convertClassPath(mp));
 		}
 		String[] cp = config.getClassPath();
-		int cpidx = -1;
 		if (cp.length > 0) {
-			cpidx = arguments.size();
 			arguments.add("-classpath"); //$NON-NLS-1$
 			arguments.add(convertClassPath(cp));
 		}
@@ -395,26 +394,18 @@
 			}
 
 		}
-
 		if (isModular(config, fVMInstance)) {
 			arguments.add("-m"); //$NON-NLS-1$
 			arguments.add(config.getModuleDescription() + "/" + config.getClassToLaunch()); //$NON-NLS-1$
 		} else {
 			arguments.add(config.getClassToLaunch());
 		}
-
+		int lastVMArgumentIndex = arguments.size() - 1;
 		String[] programArgs= config.getProgramArguments();
 		addArguments(programArgs, arguments);
 
 		String[] envp = prependJREPath(config.getEnvironment());
 
-		String[] newenvp = checkClasspath(arguments, cp, envp);
-		if(newenvp != null) {
-			envp = newenvp;
-			arguments.remove(cpidx);
-			arguments.remove(cpidx);
-		}
-
 		String[] cmdLine= new String[arguments.size()];
 		arguments.toArray(cmdLine);
 
@@ -424,10 +415,15 @@
 		if (monitor.isCanceled()) {
 			return;
 		}
+		File workingDir = getWorkingDir(config);
+		ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+		if (classpathShortener.shortenCommandLineIfNecessary()) {
+			cmdLine = classpathShortener.getCmdLine();
+			envp = classpathShortener.getEnvp();
+		}
 
 		subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
 		Process p= null;
-		File workingDir = getWorkingDir(config);
 		String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
 		if(newCmdLine != null) {
 			cmdLine = newCmdLine;
@@ -462,6 +458,10 @@
 			}
 			process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
 		}
+		if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+			String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+			process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+		}
 		subMonitor.worked(1);
 		subMonitor.done();
 	}
@@ -484,71 +484,6 @@
 	}
 
 	/**
-	 * Checks to see if the command / classpath needs to be shortened for Windows. Returns the modified
-	 * environment or <code>null</code> if no changes are needed.
-	 *
-	 * @param args the raw arguments from the runner
-	 * @param cp the raw classpath from the runner configuration
-	 * @param env the current environment
-	 * @return the modified environment or <code>null</code> if no changes were made
-	 * @sine 3.6.200
-	 */
-	String[] checkClasspath(List<String> args, String[] cp, String[] env) {
-		if(Platform.getOS().equals(Platform.OS_WIN32)) {
-			//count the complete command length
-			int size = 0;
-			for (String arg : args) {
-				if(arg != null) {
-					size += arg.length();
-				}
-			}
-			//greater than 32767 is a no-go
-			//see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
-			if(size > 32767) {
-				StringBuilder newcp = new StringBuilder("CLASSPATH="); //$NON-NLS-1$
-				for (int i = 0; i < cp.length; i++) {
-					newcp.append(cp[i]);
-					newcp.append(File.pathSeparatorChar);
-				}
-				String[] newenvp = null;
-				int index = -1;
-				if(env == null) {
-					Map<String, String> nenv = DebugPlugin.getDefault().getLaunchManager().getNativeEnvironment();
-					Entry<String, String> entry = null;
-					newenvp = new String[nenv.size()];
-					int idx = 0;
-					for (Iterator<Entry<String, String>> i = nenv.entrySet().iterator(); i.hasNext();) {
-						entry = i.next();
-						String value = entry.getValue();
-						if(value == null) {
-							value = ""; //$NON-NLS-1$
-						}
-						String key = entry.getKey();
-						if(key.equalsIgnoreCase("CLASSPATH")) { //$NON-NLS-1$
-							index = idx;
-						}
-						newenvp[idx] = key+'='+value;
-						idx++;
-					}
-				}
-				else {
-					newenvp = env;
-					index = getCPIndex(newenvp);
-				}
-				if(index < 0) {
-					String[] newenv = new String[newenvp.length+1];
-					System.arraycopy(newenvp, 0, newenv, 0, newenvp.length);
-					newenv[newenvp.length] = newcp.toString();
-					return newenv;
-				}
-				newenvp[index] = newcp.toString();
-				return newenvp;
-			}
-		}
-		return null;
-	}
-
-	/**
 	 * Prepends the correct java version variable state to the environment path for Mac VMs
 	 *
 	 * @param env the current array of environment variables to run with
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
index 5e910b6..873e5c6 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
@@ -369,8 +369,12 @@
 	public static final String ATTR_EXCLUDE_TEST_CODE = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_EXCLUDE_TEST_CODE"; //$NON-NLS-1$
 
 	/**
-	 * Status code indicating a launch configuration does not
-	 * specify a project when a project is required.
+	 * @since 3.11
+	 */
+	public static final String ATTR_USE_CLASSPATH_ONLY_JAR = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_USE_CLASSPATH_ONLY_JAR"; //$NON-NLS-1$
+
+	/**
+	 * Status code indicating a launch configuration does not specify a project when a project is required.
 	 */
 	public static final int ERR_UNSPECIFIED_PROJECT = 100;
 
@@ -540,6 +544,11 @@
 	public static final int ERR_PROJECT_CLOSED = 124;
 
 	/**
+	 * @since 3.11
+	 */
+	public static final int ERR_CLASSPATH_TOO_LONG = 125;
+
+	/**
 	 * Status code indicating an unexpected internal error.
 	 */
 	public static final int ERR_INTERNAL_ERROR = 150;