Our applications contain multiple main classes via dependent jar library. (e.g.: a dependent jar contains simple tool app to run db upgrade.)

Currently, [Jar|War]Launcher reads Start-Class from manifest file and simply fails if it doesn't exist. Since [Jar|War]Launcher is used for fat jar, java -jar method cannot run different main class when there are other main classes available in the jar(maybe nested jar).

Workaround is to use PropertiesLauncher with java -cp.

java -cp app.jar -Dloader.main=com.example.AnotherApplication  org.springframework.boot.loader.PropertiesLauncher

Similar to jarmode, it would be nice to allow [Jar|War]Launcher to take special system property to allow overriding start-class.

e.g.:

java -jar -Dstart-class=com.example.AnotherApplication app.jar

This way, user can just use java -jar instead of java -cp ... with PropertiesLauncher. Since many users simply use java -jar, it is convenient and discoverable option than PeropertiesLauncher.

Comment From: wilkinsona

Thanks for the suggestion.

Rather than supporting this in the jar and war launchers, which would blur the lines between them and the PropertiesLauncher, I think it would be better handled in your application's main method. You could use a command line argument or system property of your choosing to control the behaviour of your application. For example, you could run different applications based on a system property:

public static void main(String[] args) {
    String appMode = System.getProperty("appMode");
    if (appMode.equals("alpha")) {
        SpringApplication.run(AlphaApplication.class, args);
    }
    else if (appMode.equals("bravo")) {
        SpringApplication.run(BravoApplication.class, args);
    }
    else if (appMode.equals("charlie")) {
        SpringApplication.run(CharlieApplication.class, args);
    }
    else {
        System.err.println("Unrecognized appMode '" + appMode + "'");
        System.exit(1);
    }
}

Another option that would give more control over exactly how each mode behaves would be to delegate to a main method:

public static void main(String[] args) {
    String appMode = System.getProperty("appMode");
    if (appMode.equals("alpha")) {
        AlphaApplication.main(args);
    }
    else if (appMode.equals("bravo")) {
        BravoApplication.main(args);
    }
    else if (appMode.equals("charlie")) {
        CharlieApplication.main(args);
    }
    else {
        System.err.println("Unrecognized appMode '" + appMode + "'");
        System.exit(1);
    }
}

An advantage of this approach over a system property that controls the start class is that users don't need to know the fully qualified name of each main class. Instead, they can use values that are shorter, easier to remember, and that do not expose something that could be considered an implementation detail of the app.

Comment From: ttddyy

Thanks for suggestion.

Since I provide the launcher as library, I don't have reference to concrete application classes. So, I just simply put this class on top of spring-boot's jar/war launcher in my library:

public class MyLauncher {

  public static void main(String[] args) throws Exception {
    String mainClassName = System.getProperty("launch");
    if (mainClassName == null) {
      System.err.println("System property 'launch' is not defined.");
      System.exit(1);
    }

    // Launcher#launch from spring-boot put its classloader(LaunchedURLClassLoader)
    // to current context classloader. Therefore, it is available in this thread and
    // MainMethodRunner uses it to launch the application, which allows to read
    // resources from executable jar file.
    MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClassName, args);
    mainMethodRunner.run();
  }
}

To use this launcher, Start-Class entry needs to point to this class. Then, spring-boot's jar/war launcher simply calls this with its classloader and here just dispatches the main class based on the launch parameter.

This implementation has limitation that whenever calling java -jar command launch parameter is always required. So, I put this in library, but whether to use this or other mechanism is left to application teams's decision.

This is my side update and fyi if anybody reading this issue might want to use this impl in future.

Comment From: philwebb

@ttddyy You might want to look at the jarmode support we're adding to 2.3. It might help with your use-cae. Some docs for how we use it are here: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#writing-the-dockerfile. The org.springframework.boot.loader.jarmode.JarMode interface is the main entrypoint.

Comment From: ttddyy

Thanks @philwebb

Yeah, I can provide a custom impl since JarMode handling logic is loaded from spring factories. Thanks for the idea. I'll explore it when I get a chance.

The new layertools jarmode would unify our docker layering strategy which is currently vary project by project. It will be another task for me to provide layertools guideline to application teams when boot 2.3 is released. We benefit updates from this area. So, thanks!!