Deploying a Java Application

This guide shows how to adapt the Deploying an Application article for the Java programming language. Be sure to read the Primer and parent article as we skim over many topics covered in more detail there.

You can find the sample app in our public source code repository (basic-examples/deployapp/ALU).

Sample Java App

Our Java application will be called ALU, a reference to arithmetic logic units (ALUs) found in CPUs. There will be one file, called Adder.java, which will read two numbers from its command-line arguments, and print their summation to standard out.

Contents of Adder.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package ALU;

public class Adder {

        public static void main(String args[]) {

                if(args.length != 2) {
                        System.err.println("You need to pass " +
                                "two arguments to be added");
                        System.exit(1);
                }

                int x = Integer.parseInt(args[0]);
                int y = Integer.parseInt(args[1]);
                System.out.println(Integer.toString(x + y));

                System.exit(0);
        }

}

Assuming you have the Java Development Kit (JDK) and Java Runtime Environment (JRE) setup on your machine, you can compile and run Adder.java by running following shell commands:

# compile Adder
$ javac ALU/Adder.java

# run Adder with arguments 1 and 2
$ java ALU.Adder.java 1 2
3

Create an App

Create a directory called ALU containing Adder.java:

ALU/
ALU/Adder.java

Note that when compiling Adder.java, a new file Adder.class is created:

$ javac ALU/Adder.java
$ ls ALU
Adder.java
Adder.class

To deploy the app on PiCloud, we first create a Volume for the ALU application.

$ picloud volume create ALU ALU

Now from outside the ALU directory, run the following command to synchronize the directory with your volume.

$ picloud volume sync ALU/ ALU:

Note

Make sure you compile before syncing. Compiling creates the .class files, which are required to actually execute your program.

Running your App

To run ALU on the cloud, we’ll use picloud exec:

$ picloud exec -v ALU java ALU.Adder 1 2
[jid]

Notice how the command java ALU.Adder is identical to the command used to run it locally. picloud exec is used to run the command on the cloud, and -v ALU specifies that the command should use the ALU volume you just created.

To get the result of the job, use picloud result.

$ picloud result [jid]
3

Perfect! You’ve just summed numbers in the cloud.

What if Adder Error-ed?

As you can see from the sample code, if Adder is not passed two arguments, then it has a non-zero return code, and writes to standard error.

# run Adder incorrectly
$ java ALU.Adder
You need to pass two arguments to be added

# check the return code
$ echo $?
1

If we ran this on PiCloud, we would see the following:

$ picloud exec -v ALU java ALU.Adder
[jid]

$ picloud result [jid]
CloudException: command terminated with nonzero return code 1
stderr follows:
You need to pass two arguments to be added

$ picloud status [jid]
error

As you can see, the result states the return code, and the standard error. Also, querying the status of the job clearly shows that the job errored.

Mapping

A common use case is to run the same program with different input arguments in parallel. This is the easiest way to leverage ten or even a thousand cores. To do this, we’ll use Mapping.

We’ll run the adder, but with three different sets of input arguments: (1, 2), (3, 4), (5, 6).

$ picloud mapexec -v ALU -n x=1,3,5 -n y=2,4,6 java ALU.Adder {x} {y}
[jid1-jid3]

This creates three jobs represented by range of jids. These jobs have entered our queue, and will be assigned to free cores as soon as possible. To understand how our queueing works, see Realtime Cores.

$ picloud result [jid1-jid3]
Result for jid [jid1]:
3

Result for jid [jid2]:
7

Result for jid [jid3]:
11

As we can see, all jobs processed successfully.

Using Data Files

We’re now going to extend the above example with a new class FileAdder, which sums the numbers in a file. FileAdder will accept 1 argument, the name of a file that contains numbers delimited by newlines. Here’s the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package ALU;

import java.io.*;

public class FileAdder {

     public static void main(String args[]) {

             if(args.length != 1) {
                     System.err.println("You need to pass " +
                                        "a filename as an argument");
                     System.exit(1);
             }

             int sum = 0;

             try {
                     // open file and sum numbers
                     BufferedReader in = new BufferedReader(new FileReader(args[0]));
                     String line;
                     while ((line = in.readLine()) != null) {
                             sum += Integer.parseInt(line);
                     }
                     in.close();

                     // print sum to stdout
                     System.out.println(Integer.toString(sum));

                     System.exit(0);

             } catch (IOException e) {

                     System.err.println("Error reading file");
                     System.exit(1);

             }

     }

}

Now create a file called numbers.dat with the following contents:

1
2
3
4
5
6
7
8
9
10

Compiling and running FileAdder locally, we get the expected result:

$ javac ALU/FileAdder.java
$ java ALU.FileAdder numbers.dat
55

To run FileAdder on the cloud, we need to get numbers.dat copied to the working directory of the job. To do this, we use the -d flag along with the @ symbol, as described in Using a File from Your Machine.

$ picloud exec -v ALU -d file=@numbers.dat java ALU.FileAdder {file}
[jid]
$ picloud result [jid]
55

Using Your Bucket for Input Data

In addition to upload size limitations, it may be undesirable to upload data files using the -d flag since it sends the file upon each picloud exec invocation. Instead, add your data files to your Bucket so that they are only uploaded once.

First, let’s save numbers.dat as an object in your bucket:

$ picloud bucket put numbers.dat numbers.dat

Let’s create ObjectAdder, which will:

  1. Accept one command-line argument, an object name.
  2. Download the object to the local directory.
  3. Sum the numbers in the file.

Here’s the code for ObjectAdder.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package ALU;

import java.io.*;

public class ObjectAdder {

     public static void main(String args[]) {

             if(args.length != 1) {
                     System.err.println("You need to pass " +
                             "an object name as an argument");
                     System.exit(1);
             }

             int sum = 0;

             try {
                     // download object from bucket
                     Runtime.getRuntime().exec("picloud bucket get " + args[0] + " .").waitFor();
             } catch (IOException e) {
                     System.err.println("Could not download object");
                     System.exit(1);
             } catch (InterruptedException e) {
                     System.err.println("Could not download object");
                     System.exit(1);
             }

             try {
                     // open file and sum numbers
                     BufferedReader in = new BufferedReader(new FileReader(args[0]));
                     String line;
                     while ((line = in.readLine()) != null) {
                             sum += Integer.parseInt(line);
                     }
                     in.close();

                     // print sum to stdout
                     System.out.println(Integer.toString(sum));

                     System.exit(0);

             } catch (IOException e) {

                     System.err.println("Error reading file");
                     System.exit(1);

             }

     }

}

Note how we use the standard Runtime class to execute the picloud bucket get CLI command. Let’s now execute ObjectAdder in the cloud:

If we run the code locally, you’ll notice it takes longer. The reason is because numbers.dat is being downloaded to your local machine from your bucket:

# compile ObjectAddr
$ javac ALU/ObjectAddr.java

$ java ALU.ObjectAddr numbers.dat
55

Let’s sync ObjectAddr and run it on the cloud:

# sync ObjectAddr to your volume
$ picloud volume sync ALU/ ALU:

$ picloud exec -v ALU java ALU.ObjectAdder numbers.dat
[jid]

$ picloud result [jid]
55

As expected, we get the correct result.