Repository: GoogleContainerTools/jib
File: jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarProcessor.java (Java)

Code before:
    FileEntriesLayer resourcesLayer =
        addDirectoryContentsToLayer(
            RESOURCES,
            localExplodedJarRoot,
            isResourceFile,
            APP_ROOT.resolve(RelativeUnixPath.get("explodedJar")));

    layers.add(resourcesLayer);
    layers.add(classesLayer);
    return layers;
  }

  /**
   * Compute the entrypoint for a standard jar in exploded mode.
   *
   * @param jarPath path to jar file
   * @param tempDirPath path to temporary jib local directory
   * @param layers list of {@link FileEntriesLayer}
   * @return list of {@link String} representing entrypoint
   * @throws IOException if I/O error occurs when opening the jar file or if temporary directory
   *     provided doesn't exist
   * @throws IllegalArgumentException if main class is not found in the jar manifest
   */
  public static ImmutableList<String> computeEntrypoint_explodedStandard(
      Path jarPath, Path tempDirPath, List<FileEntriesLayer> layers)
      throws IOException, IllegalStateException {
    Path localExplodedJarRoot = tempDirPath;
    ZipUtil.unzip(jarPath, localExplodedJarRoot);

    String mainClass = null;
    try (JarFile jarFile = new JarFile(jarPath.toFile())) {
      mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
    }
    if (mainClass == null) {
      throw new IllegalArgumentException("Main-Class not found in jar's manifest.");
    }
    List<String> classpath = new ArrayList<>();
    for (FileEntriesLayer layer : layers) {
      if (layer.getName().equals(DEPENDENCIES) || layer.getName().equals(SNAPSHOT_DEPENDENCIES)) {
        classpath.add(APP_ROOT.resolve("dependencies").toString());
        break;
      }
    }
    classpath.add(APP_ROOT.resolve("explodedJar").toString());
    String classPathString = String.join(":", classpath);
    return ImmutableList.of("java", "-cp", classPathString, mainClass);
  }

  private static FileEntriesLayer addDirectoryContentsToLayer(
      String layerName,
      Path sourceRoot,

Review comment (bug):
I admit we don't have a good established policy around throwing, handling, and reporting exceptions and errors. We have not done well so far. But at least the situation for Jib CLI is not so grim, as it is not a library but a CLI. So I think throwing a runtime exception to crash for this type of errors is more or less acceptable.

Anyways, what I wanted to say is that, I think it's fine to not specify `IllegalStateException` (or `IllegalArgumentException`). Also in the Javadoc.

Code after:
        addDirectoryContentsToLayer(
            RESOURCES,
            localExplodedJarRoot,
            isResourceFile,
            APP_ROOT.resolve(RelativeUnixPath.get("explodedJar")));

    layers.add(resourcesLayer);
    layers.add(classesLayer);
    return layers;
  }

  /**
   * Computes the entrypoint for a standard jar in exploded mode.
   *
   * @param jarPath path to jar file
   * @return list of {@link String} representing entrypoint
   * @throws IOException if I/O error occurs when opening the jar file
   */
  static ImmutableList<String> computeEntrypointForExplodedStandard(Path jarPath)
      throws IOException {
    try (JarFile jarFile = new JarFile(jarPath.toFile())) {
      String mainClass =
          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
      if (mainClass == null) {
        throw new IllegalArgumentException(
            "`Main-Class:` attribute for an application main class not defined in the input Jar's manifest (`META-INF/MANIFEST.MF` in the Jar).");
      }
      String classpath = APP_ROOT + "/explodedJar:" + APP_ROOT + "/dependencies/*";
      return ImmutableList.of("java", "-cp", classpath, mainClass);
    }
  }

  private static FileEntriesLayer addDirectoryContentsToLayer(
      String layerName,
      Path sourceRoot,
      Predicate<Path> pathFilter,
      AbsoluteUnixPath basePathInContainer)
      throws IOException {
    FileEntriesLayer.Builder builder = FileEntriesLayer.builder().setName(layerName);
    new DirectoryWalker(sourceRoot)
        .filterRoot()
        .filter(path -> Files.isDirectory(path) || pathFilter.test(path))
        .walk(
            path -> {
              AbsoluteUnixPath pathOnContainer =
                  basePathInContainer.resolve(sourceRoot.relativize(path));
              builder.addEntry(path, pathOnContainer);
            });
    return builder.build();
  }
}