DEV Community

Malek Ramdani
Malek Ramdani

Posted on • Updated on

When Java meet Docker... Part I

In this post, we are going to learn the very basics of how to run a java application inside a Docker container, we will start with a few definitions, and then we will build a small silly Java application which will, at the end of the post, run in a container.

This, will be a first post of a series about Java and Docker, our application will evolve along the series, until we reach the cloud ☁️ !! So let's start the journey ✈️

Cloud-Native

Before we start talking about Docker and Java, let's have a few words about Cloud-Native, a term we hear often in the IT world nowadays. So what cloud-native means ?
Here is how CNCF (Cloud Native Computing Foundation) defines it :

Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.

These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil.

In other words, Cloud-Native is a set of principles and technologies we should follow and use to build highly available, scalable, reliable and resilient software, and all of this with speed and agility.

But, isn't cloud, cloud-native ? Well, we need to differentiate between the two, as explained by Cornelia Davis in her book Cloud Native Patterns, when talking about differentiating the cloud-native from the cloud :

the latter is about where, whereas the former is about how and is the really interesting part).

Docker

But what docker have to do with all of this ? Wait, we are almost there 😊, one of the pillar of cloud-native is the containerization, and the most popular software to manage containers and ship applications into it is Docker !!

If the concept of containerization is a decade old in Linux systems (cgroups and kernel namespaces), the emergence of Docker accelerated the use of this technology. A glimpse of docker underlying technology can be found in the official documentation :

Docker is written in the Go programming language and takes advantage of several features of the Linux kernel to deliver its functionality. Docker uses a technology called namespaces to provide the isolated workspace called the container. When you run a container, Docker creates a set of namespaces for that container.

These namespaces provide a layer of isolation. Each aspect of a container runs in a separate namespace and its access is limited to that namespace.

What are we going to build

Now that we have the context, we can start working on our Java application and run it in a container later on.

First, we are going to code a very simple application that query a MySQL database and get a list of football (Soccer in North America ) players. Then we will create a jar file from this application and write a dockerfile to create a Docker image and then run container where our application will be executed.

We will also deploy MySQL database in a Docker container, run everything and see the magic.

At the end, our application should looks to something like this :

Image description

MySQL Database with Docker

First, let's start with the database, we are going to run a MySQL container with our user and password, and then go inside the container to create our schema, table and some dummy data.
Our schema will have only on table "players" :

Image description

Before we run the database container, we need to create a network on which our containers will be running, in fact when containers are created by Docker they are isolated and do not communicate with each other, this why we need to specify that the containers are on the same network in order to enable communication between them.
When we create a new network, Docker by default will create it as a bridge netwrok, to do this let's run this command :

docker network create mynet

the output of this command should be a long alhphanumeric string (f9bc75b98986ac....)
You can type docker network ls and you will see your network newly created, you should see something like this :

Image description

Now that we have our network, let's run our MySQL container. For this, we are going to use the official MySQL Docker image, and execute this command :
docker run --name playersdb --network mynet -e MYSQL_ROOT_PASSWORD=1234 -d mysql:latest

The command above will run a container from mysql image docker run with the latest version mysql:latest, the name of the container will be playersdb --name playersdb, the container will run on a network mynet --network mynet, the root password will be '1234' -e MYSQL_ROOT_PASSWORD=1234 (-e is used to determine the environment variables of the container) and the container will run in a detached mode (in the background) -d

Let's check our container now, using docker ps :

Image description
Cool, our database container is now up and running, what we need to do now is to connect to our instance and create the schema we will use in our application.
we could have used the port mapping by adding the option -p 3306:3306 in the Docker command above and would have used mysql workbench to manage the database. Instead we will use docker, specifically the "docker exec" command to open a mysql cli session into the container. To do this we need to run this command :
docker exec -it playersdb mysql -uroot -p1234
here we should find ourselves in the mysql cli, now we can copy past the sql code below, it will create a schema playersdb, a user juser*, our table **players as described above and finally insert some data into our db :

CREATE DATABASE `playersdb` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
CREATE USER 'juser'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON * . * TO 'juser'@'%';
FLUSH PRIVILEGES;

USE playersdb;

CREATE TABLE `players` (
  `id` int DEFAULT NULL,
  `lastname` varchar(255) DEFAULT NULL,
  `firstname` varchar(255) DEFAULT NULL,
  `team` varchar(255) DEFAULT NULL,
  `position` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `playersdb`.`players`
(`id`,
`lastname`,
`firstname`,
`team`,
`position`)
VALUES
(1,
'Ryad',
'Boudebouz',
'Algeria',
'RW'),
(2,
'Manuel',
'Ruicosta',
'Portugal',
'CM'),
(3,
'Fernando',
'Redondo',
'Argentina',
'DM'),
(4,
'Ricardo',
'Kaka',
'Brazil',
'CM'),
(5,
'Ronaldo',
'TheOnlyRonaldo',
'Brazil',
'CF'),
(6,
'Paolo',
'Maldini',
'Italy',
'CB');
Enter fullscreen mode Exit fullscreen mode

In order to check if the sql script executed without issue, you can run a select query select * from playersdb.players, the result below should be displayed :

Image description

Our Java application

Now that our database, schema and data are all set, we can jump to our Java application.
This will be a basic application that will connect to the database using JDBC, and run a select query on players table and display the result.

Let's start by creating simple project using Maven :

mvn archetype:generate -DgroupId=com.players.app -DartifactId=players-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
Enter fullscreen mode Exit fullscreen mode

This command will create a simple Java project with an App.java class. The first thing we need to do is to modify our pom.xml by adding the jdbc driver for MySQL database as a dependency and the build plugins to generate a Jar file.

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.22</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
<build>
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
      <archive>
        <manifest>
          <mainClass>com.players.app.App</mainClass>
        </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with dependencies</descriptorRef>
          </descriptorRefs>
    </configuration>
    </plugin>
    <plugin>
    <!-- Build an executable JAR -->
      <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
      <version>3.1.0</version>
        <configuration>
          <archive>
            <manifest>                   
                  <addClasspath>true</addClasspath>              
                    <classpathPrefix>lib/</classpathPrefix>                          
                      <mainClass>com.players.app.App
                        </mainClass>
        </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
</build>
Enter fullscreen mode Exit fullscreen mode

Now, we can write some Java code, our application is a silly application which will connect to the playersdb database (we ran on a container previously), execute an sql query and display the result,
This is the code of our Java class, you will find the github url at the end of this post.

public class App {
    // in the dbhost the first playersdb is the container name and the second one is the database name
    private static String connString = "jdbc:mysql://playersdb:3306/playersdb";
    private static String username = "juser";
    private static String password = "password";
    private static Connection conn;
    private static String sql_req = "select * from players";
    private static Statement stmt;
    private static ResultSet results;

    public static void main(String[] args) {

        try {
            conn = DriverManager.getConnection(dbhost, username, password);
            System.out.println("Connecting...");
            System.out.println("Connected !!");

            stmt = conn.createStatement();
            results = stmt.executeQuery(sql_req);

            while (results.next()) {
                System.out.println("Id = " + results.getInt("id") + "\n" + "FirstName = "
                        + results.getString("firstname") + "\n" + "LastName = " + results.getString("lastname") + "\n"
                        + "Team = " + results.getString("team") + "Position = " + results.getString("position"));

            }
        } catch (SQLException e) {
            System.out.println("Cannot connect to database connection");
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip : we can test our java application locally, to do this all we need to do is run the mysql container with a port mapping option -p 3306:3306, and replace the host name in connection string by localhost or 127.0.0.1, which will give us jdbc:mysql://localhost:3306/playersdb

Once this is done, we can run maven to generate the Jar file, to do this you can run maven from your IDE (Eclipse, IntelliJ, VScode...) or you can simply open your terminal (CMD in windows, terminal in linux...) go to the project folder and run :
mvn assembly:single
This will build the project and will create a Jar file in the target folder. This is what you should see after the build finish:

Image description

You can see that a Jar file with the name players-app-1.0-jar-with-dependencies.jar has been created.

Our java application containerization with Docker

At this point, we have our MySQL container ready, we have our jar file as well, all we need now, is a container in which our jar will be executed. For this let's create a new folder, copy in it the Jar file we created above, for simplification let's rename it players.jar. In the same folder let create a file called dockerfile, this file will contains the instructions to create a Docker image with our specific needs, in this case, an image that contains our jar file which will be executed every time we run the container from this image.
Let's open our dockerfile with a text editor and paste into it those instructions :

FROM openjdk:11

COPY /players.jar /players.jar

CMD ["java", "-jar", "/players.jar"]

Enter fullscreen mode Exit fullscreen mode

So what those instructions means ? the FROM instruction set the base image, a valid Docker file must start with a FROM.
Then COPY will copy our jar file into the image.
And finally CMD, this instruction will provides defaults for an executing container, in our case, every time we run the container the command java -jar players.jar will be executed, this command we usually use to execute a jar file.

Once our dokcerfile created, we can create our image by executing this command :
docker build -t playersapp:1.0 .
This command will build our image file from the dockerfile.

Now we can see that our image playersapp with the tag 1.0 is available, to check this we need to run docker images :

docker images

We are almost there !! Now, all will need to do is to run a container from our image playersapp:1.0, this container need to be in the same network than our MySQL container, so let's run this command :
docker run --name playersapp --network mynet playersapp:1.0
And now we can see the list of the players we created before in this post :

Image description

Conclusion

We have reached the end of this tutorial, although the application is very simple, the objective here is have an idea about what cloud-native is, and a step-by-step to follow to run a java program from a container.

In the next post, our application will evolve into a spring boot application, and we will use docker compose. So see you soon ✌️

Discussion (0)