In this post, I am delighted to share my journey of seamlessly integrating Java programming within Jupyter notebooks.


Setup

Commencing with the selection of a pertinent Jupyter Docker Stack image, as detailed in the Jupyter Docker Stacks documentation, the following Docker command initializes the setup:

docker pull quay.io/jupyter/minimal-notebook:notebook-7.0.6

Subsequently, the Docker image is run on a Windows WSL environment, with the host IP set to 192.168.68.114:

docker run -p 192.168.68.114:10000:8888 -v ~/work:/home/jovyan/work quay.io/jupyter/minimal-notebook:notebook-7.0.6

jupyter-java-docker-start

The Jupyter environment becomes accessible by navigating to:

http://192.168.68.114:10000/lab?token=52212ee09c057a6c8ef0c6db38eed07bf47037f76a2d8199

jupyter-notebook

Further, the Docker image is tagged and pushed to a local registry:

docker tag quay.io/jupyter/minimal-notebook:notebook-7.0.6 registry.local:5000/jupyter-minimal-notebook:notebook-7.0.6
docker push registry.local:5000/jupyter-minimal-notebook:notebook-7.0.6

Deployment in HomeLab

The deployment in the HomeLab environment commences with the creation of a designated namespace:

kc create ns jupyter

Following this, the deployment configuration is provided via a deploy.yaml file:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jupyter-minimal-notebook
  namespace: jupyter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jupyter-minimal-notebook
  template:
    metadata:
      labels:
        app: jupyter-minimal-notebook
    spec:
      containers:
      - name: jupyter-minimal-notebook
        image: registry.local:5000/jupyter-minimal-notebook:notebook-7.0.6
        ports:
        - containerPort: 8888
        volumeMounts:
        - name: jupyter-store
          mountPath: /work
          subPath: ./jupyter-work
      imagePullSecrets:
      - name: regcred
      volumes:
      - name: jupyter-store
        persistentVolumeClaim:
            claimName: jupyter-nfs

Additionally, a svc.yaml file specifies the service configuration:

apiVersion: v1
kind: Service
metadata:
  name: jupyter-minimal-notebook-svc
  namespace: jupyter
spec:
  selector:
    app: jupyter-minimal-notebook
  type: LoadBalancer
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8888

Upon successful application of these changes in the HomeLab, the application logs yield the following output:

[C 2024-01-20 15:35:02.221 ServerApp] 
    
    To access the server, open this file in a browser:
        file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
    Or copy and paste one of these URLs:
        http://jupyter-minimal-notebook-b4fb98bbd-cxttl:8888/lab?token=2251c5c06cdfc487297057534dbc62de7589bcfa24f6be4a
        http://127.0.0.1:8888/lab?token=2251c5c06cdfc487297057534dbc62de7589bcfa24f6be4a
[I 2024-01-20 15:35:03.813 ServerApp] Skipped non-installed server(s): bash-language-server, dockerfile-language-server-nodejs, javascript-typescript-langserver, jedi-language-server, julia-language-server, pyright, python-language-server, python-lsp-server, r-languageserver, sql-language-server, texlab, typescript-language-server, unified-language-server, vscode-css-languageserver-bin, vscode-html-languageserver-bin, vscode-json-languageserver-bin, yaml-language-server

In this instance, the external IP is recorded as 192.168.68.225. Access to Jupyter is achieved by navigating to:

http://192.168.68.225/lab?token=2251c5c06cdfc487297057534dbc62de7589bcfa24f6be4a

Java Kernel Integration

The integration of Java within the Jupyter notebook environment is facilitated by JBang, a tool simplifying the creation, editing, and execution of Java programs. Following the guidelines provided by Jupyter Java, let’s create a new notebook within the designated work folder.

!pip install jbang
import jbang
jbang.exec("trust add https://github.com/jupyter-java")
jbang.exec("install-kernel@jupyter-java --java 17")

jupyter-install-jbang-java-17

The Java Kernel can be selected seamlessly:

jupyter-switch-to-java-kernel

Testing with Java Kernel

A simple “Hello World” program is executed in the new IJava Notebook:

System.out.println("Hello World!")

jupyter-java-hello-world

Next, let’s try with importing the commons-lang3 library:

%%loadFromPOM
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.0</version>
</dependency>

Here’s the sample Java code snippet which demonstrates the usage of the Apache Commons Lang library:

import org.apache.commons.lang3.StringUtils;

String sampleString = "Hello, Apache Commons!";

// Check if the string is empty or null using Commons Lang
if (StringUtils.isNotEmpty(sampleString)) {
    System.out.println("The string is not empty or null.");
} else {
    System.out.println("The string is empty or null.");
}

jupyter-java-import-commons-lang3


Exploring Java in PyKernel

Referencing to the JBang Jupyter tutorial, an alternate method to experiment with Java involves using JBang within the Python kernel:

import os

if os.name == 'nt':
    !iex "& { $(iwr https://ps.jbang.dev) } app setup"
else:
    !curl -Ls https://sh.jbang.dev | bash -s - app setup

os.environ["PATH"] = os.path.expanduser('~/.jbang/bin') + os.pathsep + os.environ["PATH"]

jupyter-pykernel-jbang

A Java program is created using the following command:

!jbang init -t cli src/hello.java

This generates the hello.java program:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3


import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

import java.util.concurrent.Callable;

@Command(name = "hello", mixinStandardHelpOptions = true, version = "hello 0.1",
        description = "hello made with jbang")
class hello implements Callable<Integer> {

    @Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
    private String greeting;

    public static void main(String... args) {
        int exitCode = new CommandLine(new hello()).execute(args);
        System.exit(exitCode);
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        System.out.println("Hello " + greeting);
        return 0;
    }
}

Similarly, the previous StringChecker.java is refactored:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.commons:commons-lang3:3.0

import org.apache.commons.lang3.StringUtils;

public class StringChecker {  

    public static void main(String[] args) {
        String sampleString = "Hello, Apache Commons!";
        
        // Check if the string is empty or null using Commons Lang
        if (StringUtils.isNotEmpty(sampleString)) {
            System.out.println("The string is not empty or null.");
        } else {
            System.out.println("The string is empty or null.");
        }
    }
}

Execution of the programs is accomplished with the following commands:

!jbang src/StringChecker.java
!jbang src/hello.java

jupyter-pykernel-jbang-sample-codes

Thank you for joining me on this Java-in-Jupyter journey. May your coding endeavors be seamless, exploratory, and filled with the joy of discovery. Happy coding!