Search code examples
javadockerpackagedockerfilelocal

Create and Run a Docker Image for Java Application with Packages


I have managed to setup and run a Docker Image for a simple Hello World Java console application, but I am struggling with it when I add packages. I have this working:

Folder contents:

Dockerfile  HelloWorld.class  HelloWorld.java

Dockerfile:

FROM openjdk:11-jdk-slim
WORKDIR /app
COPY HelloWorld.java .
RUN javac HelloWorld.java
CMD ["java", "HelloWorld"]

Java class:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Docker World!");
    }
}

This works fine with

sudo docker build -t hello-world-java .

sudo docker run hello-world-java

But if I add a package for the class to the folder contents and the class:

Java class:

package hello;
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Docker World!");
    }
}

Contents

~/workspace/helloWorldPackage$ tree 
.
├── Dockerfile
└── hello
    ├── HelloWorld.class
    └── HelloWorld.java

2 directories, 3 files

And modify Dockerfile like:

FROM openjdk:11-jdk-slim
WORKDIR /app
COPY hello/ .
RUN javac HelloWorld.java
CMD ["java", "hello.HelloWorld"]

I can docker build it, but when running I get:

Error: Could not find or load main class hello.HelloWorld
Caused by: java.lang.ClassNotFoundException: hello.HelloWorld
exit status 1

I have tried several modifications in the Dockerfile to no avail. Mostly modifying the classpath on the CMD, or what it is copied in COPY/WORKDIR, but I think i do not understand exactly what such commands do. Is CMD run from inside the folder at WORKDIR? How does it interact with the COPY folder? And with the java command? I think there is something I lack there to make it work.

I can only find tutorials or answers for Java web apps or Java local apps without packages. I am expecting a similar behavior to what I get for a packageless Java application. I.e. the correct Dockerfile description for java to catch the correct classpath.


Solution

  • When you try to launch hello.HelloWorld as the main class, the JVM looks (among other places) for a hello/HelloWorld.class file. You need to keep that hello structure when you copy files into the container.

    The COPY line should include the entire hello directory. The RUN javac ... line should use a relative path to the source file. Together this looks like

    FROM openjdk:11-jdk-slim
    WORKDIR /app
    COPY hello/ hello/               # relative path on right-hand side
    RUN javac hello/HelloWorld.java  # relative path in filename
    CMD ["java", "hello.HelloWorld"]
    

    (For any larger project you may want a more substantial build system, like Maven or Gradle. For Java it's not uncommon to build the application outside Docker and only COPY the jar file into the image; or if you're running the build in the Dockerfile, make sure to COPY the entire src tree in standard project layouts.)