diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md
index 59c426eb4d..5f5d6a03f6 100644
--- a/jib-core/CHANGELOG.md
+++ b/jib-core/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Added
- Adds support for configuring volumes ([#1121](https://github.com/GoogleContainerTools/jib/issues/1121))
+- Adds `JavaContainerBuilder` for building opinionated containers for Java applications ([#1212](https://github.com/GoogleContainerTools/jib/issues/1212))
### Changed
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java
new file mode 100644
index 0000000000..106eeaaef0
--- /dev/null
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2018 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.cloud.tools.jib.api;
+
+import com.google.cloud.tools.jib.filesystem.AbsoluteUnixPath;
+import com.google.cloud.tools.jib.frontend.JavaEntrypointConstructor;
+import com.google.cloud.tools.jib.frontend.JavaLayerConfigurations;
+import com.google.cloud.tools.jib.frontend.JavaLayerConfigurations.LayerType;
+import com.google.cloud.tools.jib.frontend.MainClassFinder;
+import com.google.cloud.tools.jib.image.ImageReference;
+import com.google.cloud.tools.jib.image.InvalidImageReferenceException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/** Creates a {@link JibContainerBuilder} for containerizing Java applications. */
+public class JavaContainerBuilder {
+
+ /** The default root directory of the application on the container. */
+ private static final AbsoluteUnixPath APP_ROOT = AbsoluteUnixPath.get("/app");
+
+ /** Absolute path of directory containing application resources on container. */
+ private static final AbsoluteUnixPath RESOURCES_PATH =
+ APP_ROOT.resolve(JavaEntrypointConstructor.DEFAULT_RELATIVE_RESOURCES_PATH_ON_IMAGE);
+
+ /** Absolute path of directory containing classes on container. */
+ private static final AbsoluteUnixPath CLASSES_PATH =
+ APP_ROOT.resolve(JavaEntrypointConstructor.DEFAULT_RELATIVE_CLASSES_PATH_ON_IMAGE);
+
+ /** Absolute path of directory containing dependencies on container. */
+ private static final AbsoluteUnixPath DEPENDENCIES_PATH =
+ APP_ROOT.resolve(JavaEntrypointConstructor.DEFAULT_RELATIVE_DEPENDENCIES_PATH_ON_IMAGE);
+
+ /** The entrypoint classpath element corresponding to dependencies. */
+ private static final AbsoluteUnixPath DEPENDENCIES_CLASSPATH = DEPENDENCIES_PATH.resolve("*");
+
+ /** Absolute path of directory containing additional classpath files on container. */
+ private static final AbsoluteUnixPath OTHERS_PATH = APP_ROOT.resolve("classpath");
+
+ /**
+ * Creates a new {@link JavaContainerBuilder} that uses distroless java as the base image. For
+ * more information on {@code gcr.io/distroless/java}, see the distroless repository.
+ *
+ * @return a new {@link JavaContainerBuilder}
+ * @throws InvalidImageReferenceException if creating the base image reference fails
+ * @see The distroless repository
+ */
+ public static JavaContainerBuilder fromDistroless() throws InvalidImageReferenceException {
+ return from(RegistryImage.named("gcr.io/distroless/java"));
+ }
+
+ /**
+ * Creates a new {@link JavaContainerBuilder} with the specified base image reference.
+ *
+ * @param baseImageReference the base image reference
+ * @return a new {@link JavaContainerBuilder}
+ * @throws InvalidImageReferenceException if {@code baseImageReference} is invalid
+ */
+ public static JavaContainerBuilder from(String baseImageReference)
+ throws InvalidImageReferenceException {
+ return from(RegistryImage.named(baseImageReference));
+ }
+
+ /**
+ * Creates a new {@link JavaContainerBuilder} with the specified base image reference.
+ *
+ * @param baseImageReference the base image reference
+ * @return a new {@link JavaContainerBuilder}
+ */
+ public static JavaContainerBuilder from(ImageReference baseImageReference) {
+ return from(RegistryImage.named(baseImageReference));
+ }
+
+ /**
+ * Creates a new {@link JavaContainerBuilder} with the specified base image.
+ *
+ * @param registryImage the {@link RegistryImage} that defines base container registry and
+ * credentials
+ * @return a new {@link JavaContainerBuilder}
+ */
+ public static JavaContainerBuilder from(RegistryImage registryImage) {
+ return new JavaContainerBuilder(Jib.from(registryImage));
+ }
+
+ private final JibContainerBuilder jibContainerBuilder;
+ private final JavaLayerConfigurations.Builder layerConfigurationsBuilder =
+ JavaLayerConfigurations.builder();
+ private final List jvmFlags = new ArrayList<>();
+ private final LinkedHashSet classpath = new LinkedHashSet<>(4);
+
+ @Nullable private String mainClass;
+
+ private JavaContainerBuilder(JibContainerBuilder jibContainerBuilder) {
+ this.jibContainerBuilder = jibContainerBuilder;
+ }
+
+ /**
+ * Adds dependency JARs to the image. Duplicate JAR filenames are renamed with the filesize in
+ * order to avoid collisions.
+ *
+ * @param dependencyFiles the list of dependency JARs to add to the image
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addDependencies(List dependencyFiles) throws IOException {
+ // Make sure all files exist before adding any
+ for (Path file : dependencyFiles) {
+ if (!Files.exists(file)) {
+ throw new NoSuchFileException(file.toString());
+ }
+ }
+
+ // Detect duplicate filenames and rename with filesize to avoid collisions
+ List duplicates =
+ dependencyFiles
+ .stream()
+ .map(Path::getFileName)
+ .map(Path::toString)
+ .collect(Collectors.groupingBy(filename -> filename, Collectors.counting()))
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue() > 1)
+ .map(Entry::getKey)
+ .collect(Collectors.toList());
+ for (Path file : dependencyFiles) {
+ layerConfigurationsBuilder.addFile(
+ file.getFileName().toString().contains("SNAPSHOT")
+ ? LayerType.SNAPSHOT_DEPENDENCIES
+ : LayerType.DEPENDENCIES,
+ file,
+ DEPENDENCIES_PATH.resolve(
+ duplicates.contains(file.getFileName().toString())
+ ? file.getFileName().toString().replaceFirst("\\.jar$", "-" + Files.size(file))
+ + ".jar"
+ : file.getFileName().toString()));
+ }
+ classpath.add(DEPENDENCIES_CLASSPATH.toString());
+ return this;
+ }
+
+ /**
+ * Adds dependency JARs to the image. Duplicate JAR filenames are renamed with the filesize in
+ * order to avoid collisions.
+ *
+ * @param dependencyFiles the list of dependency JARs to add to the image
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addDependencies(Path... dependencyFiles) throws IOException {
+ return addDependencies(Arrays.asList(dependencyFiles));
+ }
+
+ /**
+ * Adds the contents of a resources directory to the image.
+ *
+ * @param resourceFilesDirectory the directory containing the project's resources
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addResources(Path resourceFilesDirectory) throws IOException {
+ return addResources(resourceFilesDirectory, path -> true);
+ }
+
+ /**
+ * Adds the contents of a resources directory to the image.
+ *
+ * @param resourceFilesDirectory the directory containing the project's resources
+ * @param pathFilter filter that determines which files (not directories) should be added
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addResources(Path resourceFilesDirectory, Predicate pathFilter)
+ throws IOException {
+ return addDirectory(resourceFilesDirectory, RESOURCES_PATH, LayerType.RESOURCES, pathFilter);
+ }
+
+ /**
+ * Adds the contents of a classes directory to the image.
+ *
+ * @param classFilesDirectory the directory containing the class files
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addClasses(Path classFilesDirectory) throws IOException {
+ return addClasses(classFilesDirectory, path -> true);
+ }
+
+ /**
+ * Adds the contents of a classes directory to the image.
+ *
+ * @param classFilesDirectory the directory containing the class files
+ * @param pathFilter filter that determines which files (not directories) should be added
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addClasses(Path classFilesDirectory, Predicate pathFilter)
+ throws IOException {
+ return addDirectory(classFilesDirectory, CLASSES_PATH, LayerType.CLASSES, pathFilter);
+ }
+
+ /**
+ * Adds additional files to the classpath. If {@code otherFiles} contains a directory, the files
+ * within are added recursively, maintaining the directory structure. For files in {@code
+ * otherFiles}, files with duplicate filenames will be overwritten (e.g. if {@code otherFiles}
+ * contains '/loser/messages.txt' and '/winner/messages.txt', only the second 'messages.txt' is
+ * added.
+ *
+ * @param otherFiles the list of files to add
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addToClasspath(List otherFiles) throws IOException {
+ // Make sure all files exist before adding any
+ for (Path file : otherFiles) {
+ if (!Files.exists(file)) {
+ throw new NoSuchFileException(file.toString());
+ }
+ }
+
+ for (Path file : otherFiles) {
+ if (Files.isDirectory(file)) {
+ layerConfigurationsBuilder.addDirectoryContents(
+ LayerType.EXTRA_FILES, file, path -> true, OTHERS_PATH);
+ } else {
+ layerConfigurationsBuilder.addFile(
+ LayerType.EXTRA_FILES, file, OTHERS_PATH.resolve(file.getFileName()));
+ }
+ }
+ classpath.add(OTHERS_PATH.toString());
+ return this;
+ }
+
+ /**
+ * Adds additional files to the classpath. If {@code otherFiles} contains a directory, the files
+ * within are added recursively, maintaining the directory structure. For files in {@code
+ * otherFiles}, files with duplicate filenames will be overwritten (e.g. if {@code otherFiles}
+ * contains '/loser/messages.txt' and '/winner/messages.txt', only the second 'messages.txt' is
+ * added.
+ *
+ * @param otherFiles the list of files to add
+ * @return this
+ * @throws IOException if adding the layer fails
+ */
+ public JavaContainerBuilder addToClasspath(Path... otherFiles) throws IOException {
+ return addToClasspath(Arrays.asList(otherFiles));
+ }
+
+ /**
+ * Adds a JVM flag to use when starting the application.
+ *
+ * @param jvmFlag the JVM flag to add
+ * @return this
+ */
+ public JavaContainerBuilder addJvmFlag(String jvmFlag) {
+ jvmFlags.add(jvmFlag);
+ return this;
+ }
+
+ /**
+ * Adds JVM flags to use when starting the application.
+ *
+ * @param jvmFlags the list of JVM flags to add
+ * @return this
+ */
+ public JavaContainerBuilder addJvmFlags(List jvmFlags) {
+ this.jvmFlags.addAll(jvmFlags);
+ return this;
+ }
+
+ /**
+ * Adds JVM flags to use when starting the application.
+ *
+ * @param jvmFlags the list of JVM flags to add
+ * @return this
+ */
+ public JavaContainerBuilder addJvmFlags(String... jvmFlags) {
+ this.jvmFlags.addAll(Arrays.asList(jvmFlags));
+ return this;
+ }
+
+ /**
+ * Sets the main class used to start the application on the image. To find the main class from
+ * {@code .class} files, use {@link MainClassFinder}.
+ *
+ * @param mainClass the main class used to start the application
+ * @return this
+ * @see MainClassFinder
+ */
+ public JavaContainerBuilder setMainClass(String mainClass) {
+ this.mainClass = mainClass;
+ return this;
+ }
+
+ /**
+ * Returns a new {@link JibContainerBuilder} using the parameters specified on the {@link
+ * JavaContainerBuilder}.
+ *
+ * @return a new {@link JibContainerBuilder} using the parameters specified on the {@link
+ * JavaContainerBuilder}
+ */
+ public JibContainerBuilder toContainerBuilder() {
+ if (mainClass == null) {
+ throw new IllegalStateException(
+ "mainClass is null on JavaContainerBuilder; specify the main class using "
+ + "JavaContainerBuilder#setMainClass(String), or consider using a "
+ + "jib.frontend.MainClassFinder to infer the main class");
+ }
+ if (classpath.isEmpty()) {
+ throw new IllegalStateException(
+ "Failed to construct entrypoint because no files were added to the JavaContainerBuilder");
+ }
+
+ jibContainerBuilder.setEntrypoint(
+ JavaEntrypointConstructor.makeEntrypoint(new ArrayList<>(classpath), jvmFlags, mainClass));
+ jibContainerBuilder.setLayers(layerConfigurationsBuilder.build().getLayerConfigurations());
+ return jibContainerBuilder;
+ }
+
+ private JavaContainerBuilder addDirectory(
+ Path directory, AbsoluteUnixPath destination, LayerType layerType, Predicate pathFilter)
+ throws IOException {
+ if (!Files.exists(directory)) {
+ throw new NoSuchFileException(directory.toString());
+ }
+ if (!Files.isDirectory(directory)) {
+ throw new NotDirectoryException(directory.toString());
+ }
+ layerConfigurationsBuilder.addDirectoryContents(layerType, directory, pathFilter, destination);
+ classpath.add(destination.toString());
+ return this;
+ }
+}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/JavaLayerConfigurations.java b/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/JavaLayerConfigurations.java
index de7a206ff3..8a9dd1f593 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/JavaLayerConfigurations.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/JavaLayerConfigurations.java
@@ -160,7 +160,6 @@ public Builder addDirectoryContents(
* @throws IOException error while listing directories
* @throws NotDirectoryException if {@code sourceRoot} is not a directory
*/
- // TODO: Use in plugins
public Builder addDirectoryContents(
LayerType layerType,
Path sourceRoot,
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/JavaContainerBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JavaContainerBuilderTest.java
new file mode 100644
index 0000000000..46493641f3
--- /dev/null
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JavaContainerBuilderTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2018 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.cloud.tools.jib.api;
+
+import com.google.cloud.tools.jib.configuration.BuildConfiguration;
+import com.google.cloud.tools.jib.configuration.CacheDirectoryCreationException;
+import com.google.cloud.tools.jib.configuration.ContainerConfiguration;
+import com.google.cloud.tools.jib.filesystem.AbsoluteUnixPath;
+import com.google.cloud.tools.jib.image.InvalidImageReferenceException;
+import com.google.cloud.tools.jib.image.LayerEntry;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Resources;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assert;
+import org.junit.Test;
+
+/** Tests for {@link JavaContainerBuilder}. */
+public class JavaContainerBuilderTest {
+
+ /** Gets a resource file as a {@link Path}. */
+ private static Path getResource(String directory) throws URISyntaxException {
+ return Paths.get(Resources.getResource(directory).toURI());
+ }
+
+ /** Gets the extraction paths in the specified layer of a give {@link BuildConfiguration}. */
+ private static List getExtractionPaths(
+ BuildConfiguration buildConfiguration, String layerName) {
+ return buildConfiguration
+ .getLayerConfigurations()
+ .stream()
+ .filter(layerConfiguration -> layerConfiguration.getName().equals(layerName))
+ .findFirst()
+ .map(
+ layerConfiguration ->
+ layerConfiguration
+ .getLayerEntries()
+ .stream()
+ .map(LayerEntry::getExtractionPath)
+ .collect(Collectors.toList()))
+ .orElse(ImmutableList.of());
+ }
+
+ @Test
+ public void testToJibContainerBuilder_all()
+ throws InvalidImageReferenceException, URISyntaxException, IOException,
+ CacheDirectoryCreationException {
+ BuildConfiguration buildConfiguration =
+ JavaContainerBuilder.fromDistroless()
+ .addResources(getResource("application/resources"))
+ .addClasses(getResource("application/classes"))
+ .addDependencies(
+ getResource("application/dependencies/dependency-1.0.0.jar"),
+ getResource("application/dependencies/more/dependency-1.0.0.jar"),
+ getResource("application/dependencies/libraryA.jar"),
+ getResource("application/dependencies/libraryB.jar"),
+ getResource("application/snapshot-dependencies/dependency-1.0.0-SNAPSHOT.jar"))
+ .addToClasspath(getResource("fileA"), getResource("fileB"))
+ .addJvmFlags("-xflag1", "-xflag2")
+ .setMainClass("HelloWorld")
+ .toContainerBuilder()
+ .toBuildConfiguration(
+ Containerizer.to(RegistryImage.named("hello")),
+ MoreExecutors.newDirectExecutorService());
+
+ // Check entrypoint
+ ContainerConfiguration containerConfiguration = buildConfiguration.getContainerConfiguration();
+ Assert.assertNotNull(containerConfiguration);
+ Assert.assertEquals(
+ ImmutableList.of(
+ "java",
+ "-xflag1",
+ "-xflag2",
+ "-cp",
+ "/app/resources:/app/classes:/app/libs/*:/app/classpath",
+ "HelloWorld"),
+ containerConfiguration.getEntrypoint());
+
+ // Check dependencies
+ List expectedDependencies =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/libs/dependency-1.0.0-770.jar"),
+ AbsoluteUnixPath.get("/app/libs/dependency-1.0.0-200.jar"),
+ AbsoluteUnixPath.get("/app/libs/libraryA.jar"),
+ AbsoluteUnixPath.get("/app/libs/libraryB.jar"));
+ Assert.assertEquals(
+ expectedDependencies, getExtractionPaths(buildConfiguration, "dependencies"));
+
+ // Check snapshots
+ List expectedSnapshotDependencies =
+ ImmutableList.of(AbsoluteUnixPath.get("/app/libs/dependency-1.0.0-SNAPSHOT.jar"));
+ Assert.assertEquals(
+ expectedSnapshotDependencies,
+ getExtractionPaths(buildConfiguration, "snapshot dependencies"));
+
+ // Check resources
+ List expectedResources =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/resources/resourceA"),
+ AbsoluteUnixPath.get("/app/resources/resourceB"),
+ AbsoluteUnixPath.get("/app/resources/world"));
+ Assert.assertEquals(expectedResources, getExtractionPaths(buildConfiguration, "resources"));
+
+ // Check classes
+ List expectedClasses =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/classes/HelloWorld.class"),
+ AbsoluteUnixPath.get("/app/classes/some.class"));
+ Assert.assertEquals(expectedClasses, getExtractionPaths(buildConfiguration, "classes"));
+
+ // Check additional classpath files
+ List expectedOthers =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/classpath/fileA"),
+ AbsoluteUnixPath.get("/app/classpath/fileB"));
+ Assert.assertEquals(expectedOthers, getExtractionPaths(buildConfiguration, "extra files"));
+ }
+
+ @Test
+ public void testToJibContainerBuilder_missingAndMultipleAdds()
+ throws InvalidImageReferenceException, URISyntaxException, IOException,
+ CacheDirectoryCreationException {
+ BuildConfiguration buildConfiguration =
+ JavaContainerBuilder.fromDistroless()
+ .addDependencies(getResource("application/dependencies/libraryA.jar"))
+ .addDependencies(getResource("application/dependencies/libraryB.jar"))
+ .addDependencies(
+ getResource("application/snapshot-dependencies/dependency-1.0.0-SNAPSHOT.jar"))
+ .addClasses(getResource("application/classes/"))
+ .addClasses(getResource("class-finder-tests/extension"))
+ .setMainClass("HelloWorld")
+ .toContainerBuilder()
+ .toBuildConfiguration(
+ Containerizer.to(RegistryImage.named("hello")),
+ MoreExecutors.newDirectExecutorService());
+
+ // Check entrypoint
+ ContainerConfiguration containerConfiguration = buildConfiguration.getContainerConfiguration();
+ Assert.assertNotNull(containerConfiguration);
+ Assert.assertEquals(
+ ImmutableList.of("java", "-cp", "/app/libs/*:/app/classes", "HelloWorld"),
+ containerConfiguration.getEntrypoint());
+
+ // Check dependencies
+ List expectedDependencies =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/libs/libraryA.jar"),
+ AbsoluteUnixPath.get("/app/libs/libraryB.jar"));
+ Assert.assertEquals(
+ expectedDependencies, getExtractionPaths(buildConfiguration, "dependencies"));
+
+ // Check snapshots
+ List expectedSnapshotDependencies =
+ ImmutableList.of(AbsoluteUnixPath.get("/app/libs/dependency-1.0.0-SNAPSHOT.jar"));
+ Assert.assertEquals(
+ expectedSnapshotDependencies,
+ getExtractionPaths(buildConfiguration, "snapshot dependencies"));
+
+ // Check classes
+ List expectedClasses =
+ ImmutableList.of(
+ AbsoluteUnixPath.get("/app/classes/HelloWorld.class"),
+ AbsoluteUnixPath.get("/app/classes/some.class"),
+ AbsoluteUnixPath.get("/app/classes/main/"),
+ AbsoluteUnixPath.get("/app/classes/main/MainClass.class"),
+ AbsoluteUnixPath.get("/app/classes/pack/"),
+ AbsoluteUnixPath.get("/app/classes/pack/Apple.class"),
+ AbsoluteUnixPath.get("/app/classes/pack/Orange.class"));
+ Assert.assertEquals(expectedClasses, getExtractionPaths(buildConfiguration, "classes"));
+
+ // Check empty layers
+ Assert.assertEquals(ImmutableList.of(), getExtractionPaths(buildConfiguration, "resources"));
+ Assert.assertEquals(ImmutableList.of(), getExtractionPaths(buildConfiguration, "extra files"));
+ }
+
+ @Test
+ public void testToJibContainerBuilder_mainClassNull() throws InvalidImageReferenceException {
+ try {
+ JavaContainerBuilder.fromDistroless().toContainerBuilder();
+ Assert.fail();
+
+ } catch (IllegalStateException ex) {
+ Assert.assertEquals(
+ "mainClass is null on JavaContainerBuilder; specify the main class using "
+ + "JavaContainerBuilder#setMainClass(String), or consider using a "
+ + "jib.frontend.MainClassFinder to infer the main class",
+ ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testToJibContainerBuilder_classpathEmpty() throws InvalidImageReferenceException {
+ try {
+ JavaContainerBuilder.fromDistroless().setMainClass("Hello").toContainerBuilder();
+ Assert.fail();
+
+ } catch (IllegalStateException ex) {
+ Assert.assertEquals(
+ "Failed to construct entrypoint because no files were added to the JavaContainerBuilder",
+ ex.getMessage());
+ }
+ }
+}
diff --git a/jib-core/src/test/resources/application/dependencies/more/dependency-1.0.0.jar b/jib-core/src/test/resources/application/dependencies/more/dependency-1.0.0.jar
new file mode 100644
index 0000000000..4d14054965
--- /dev/null
+++ b/jib-core/src/test/resources/application/dependencies/more/dependency-1.0.0.jar
@@ -0,0 +1,2 @@
+]e$Ềx,
+.3I݅38VKAM)=5~'qю$[- :&% Eo7Ns`iZ0MT.9J[}?\E}UvJdo(i"Mԛ_+/cI.-O=Hi
\ No newline at end of file