HOWTO: Java development environment on a Raspberry Pi
Stephan Klünker

Contents

Introduction
System Environment
Operating System Preparations
JDK / JRE
Apache Ant and Ivy
Maven
Git
Environemnt variables
IDE: Visual Studio Code
Java project in Visual Studio Code
Jetty 10 (standalone) Setup
Basic Servlet with Jetty 10 and Maven
Spring Boot (web application example)


Introduction

This is a quick reference of how to establish a Java technology development environment on a Debian based Linux environment in general, and particularly focussed on Raspberry Pi OS, where system resources need to be used sustainably.

The page is thought to be for myself, for later re-use, however I don't see any reason for not publishing it, since it can be helpful for other developers too. For many subjects there are other solutions or alternative ways to go. I don't discuss them here, I just write down my personal preference.

I see myself as the collector of the contained solutions, not as the "author" or "inventor", a lot of contents is from other sources, which I cannot all mention or cite here, as I need to concentrate on my developer work, of which this page is a "side product".


System Environment

RaspberryPi 4, 4 GB memory, Raspberry Pi OS (Debian 10 buster), 64bit (aarch64)


Operating System Preparations

as a sudo user e.g. pi (or root): add a user (which is the developer):

      sudo adduser jade
      Lege Benutzer »jade« an ...
      Lege neue Gruppe »jade« (1002) an ...
      Lege neuen Benutzer »jade« (1002) mit Gruppe »jade« an ...
      Erstelle Home-Verzeichnis »/home/jade« ...
      Kopiere Dateien aus »/etc/skel« ...
      Geben Sie ein neues Passwort ein: 
      Geben Sie das neue Passwort erneut ein: 
      passwd: Passwort erfolgreich geändert
      Benutzerinformationen für jade werden geändert.
      Geben Sie einen neuen Wert an oder drücken Sie ENTER für den Standardwert
              Vollständiger Name []: Java Developer
	           Zimmernummer []: 
	           Telefon geschäftlich []: 
	           Telefon privat []: 
	           Sonstiges []: 
      Sind die Informationen korrekt? [J/n] J
      
The account is now ready for use, but needs to be added to the sudoers group:
      sudo visudo   
      
add the following line at the end of the edited file:
      jade ALL=(ALL) NOPASSWD: ALL
      
From now on everything every example command on this page is done by this user, unless explicitly mentioned. If superuser privileges are required, the command is running in sudo context.


JDK / JRE

Download:
    https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot
- OpenJDK 8 (LTS)
- OpenJDK 11 (LTS)
- corresponding JREs (if needed)
- Operating System: Linux
- Architecture: aarch64

Install:

         cd /usr/local
         sudo mkdir java
         cd java
         sudo cp /path/to/downloaded/jdk_and_jre/OpenJDK8U-jdk_aarch64_linux_hotspot_8u302b08.tar.gz .
         sudo cp /path/to/downloaded/jdk_and_jre/OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.12_7.tar.gz .
         ... same with JRE packages if needed ...

         sudo tar -xzf ./OpenJDK8U-jdk_aarch64_linux_hotspot_8u302b08.tar.gz
         sudo tar -xzf ./OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.12_7.tar.gz
         ... same with JRE packages if needed ...

         sudo rm ./*.tar.gz
      
         ... set environment variables ... see Environment Variables
      


Apache Ant and Ivy

Download Ant: https://ant.apache.org/bindownload.cgi
Download Ivy: http://ant.apache.org/ivy/download.cgi
- select Ivy "with dependencies" download package

Install:

      cd /usr/local
       
      sudo cp /path/to/downloaded/ant_package/apache-ant-1.10.11-bin.tar.gz .
      sudo cp /path/to/downloaded/ivy_package/apache-ivy-2.5.0-bin-with-deps.tar.gz .

      sudo tar -xzf ./apache-ant-1.10.11-bin.tar.gz
      sudo tar -xzf ./apache-ivy-2.5.0-bin-with-deps.tar.gz

      sudo mv apache-ant-1.10.11 ant
      sudo mv apache-ivy-2.5.0 ivy

      sudo rm ./*.tar.gz

      sudo cp ./ivy/ivy-2.5.0.jar ./ant/lib
    
      ... set environment variables ... see Environment Variables
      
Test the installation:
      
      cd
      mkdir project/ant_ivy_test
      cd project/ant_ivy_test
      
      vi build.xml (new file)      
      
      ant

      Buildfile: /home/user/project/ant_ivy_test/build.xml

      test:

      BUILD SUCCESSFUL
      Total time: 1 second
      


Maven

Download:
    https://maven.apache.org/download.cgi

Install:

      cd /usr/local
             
      sudo cp /path/to/downloaded/maven_package/apache-maven-3.8.1-bin.tar.gz .
      
      sudo tar -xzf ./apache-maven-3.8.1-bin.tar.gz
      
      sudo mv apache-maven-3.8.1 maven
      
      sudo rm ./*.tar.gz
          
      ... set environment variables ... see Environment Variables
      


Git

Install:

         sudo apt install git
      
Configure:
         git config --global user.name "Java Developer"
         git config --global user.email "jade@devel.somedomain.com"
         git config --global push.default simple
         git config --global branch.autosetuprebase always
         git config --global core.editor nano
         git config --global diff.tool vimdiff 
         git config --global merge.tool vimdiff
         - alternatively for diff.tool and merge.tool (requires graphic desktop):
            git config --global diff.tool meld 
            git config --global merge.tool meld
         git config --global color.ui true
         git config --global color.status auto
         git config --global color.branch auto

         check configuration
            git config --list
         
         documentation
            git help
            git help -a 
            git help -g

         browser:https://git-scm.com

         version
            git --version         
      
Create a repository:
         cd projects
         mkdir sampleproject
         cd sampleproject

         vi readme.txt
         --------------
         This is a sample Java project to be source controlled by Git.
         --------------

         vi .gitignore
         --------------
         *.[oa]
         ./target
         *.class
         *.log
         --------------

         git init
         >> Leeres Git-Repository in /home/stephan/project/sampleproject/.git/ initialisiert

         git add readme.txt
         git add .gitignore
         git commit -m "project initialized"

         cd .. (change to the projects folder)

         git clone --bare sampleproject sampleproject.git
         >> Klone in Bare-Repository 'sampleproject.git' ... Fertig.
      
Locate the repository on a server for cooperative work:
         scp -r sampleproject.git jade@reposerver:/gitrepo
         ssh jade@reposerver
         cd /gitrepo/sampleproject.git
         git init --bare --shared
         >> Reinitialized existing shared Git repository in /gitrepo/sampleproject.git/
      
- now every user in the same group / on the same (Unix) machine is able to use the repository.

Clone:
         cd path/to/projects
         git clone jade@reposerver:/gitrepo/sampleproject         
      


Environment Variables

      sudo vi /etc/envvar.sh (new file)
      - - -
      export JDK8_HOME="/usr/local/java/jdk8u302-b08"
      export JDK8_PATH="/usr/local/java/jdk8u302-b08/bin"
      export JRE8_HOME="/usr/local/java/jdk8u292-b10-jre"
      export JRE8_PATH="/usr/local/java/jdk8u292-b10-jre/bin"
      export JDK11_HOME="/usr/local/java/jdk-11.0.12+7"
      export JDK11_PATH="/usr/local/java/jdk-11.0.12+7/bin"
      export JRE11_HOME="/usr/local/java/jdk-11.0.11+9-jre"
      export JRE11_PATH="/usr/local/java/jdk-11.0.11+9-jre/bin"
      export JAVA_HOME=$JDK11_HOME
      export JAVA_PATH="$JDK11_PATH:$JRE11_PATH"
      export ANT_HOME="/usr/local/ant"
      export ANT_PATH=$ANT_HOME/bin
      export MVN_HOME="/usr/local/maven"
      export MVN_PATH=$MVN_HOME/bin      
      - - -

      sudo vi /etc/profile
      - - -
      # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
      # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

      . /etc/envvar.sh

      if [ "`id -u`" -eq 0 ]; then
         PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
      else
         PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
      fi
      export PATH

      export PATH=$PATH:$JAVA_PATH:$ANT_PATH:$MVN_PATH
      . . .
      - - -
      
       add the two bold lines to the existing /etc/profile.
       - adapt JAVA_HOME and JAVA_PATH to your needs in /etc/envvar.sh (for example if you use Java 8 instead of Java 11).


IDE: Visual Studio Code (VSC)

Install:

      sudo apt install code
      sudo apt upgrade code
      

Create a workspace: File > Add folder to workspace
File > Save workspace as ... - e.g. [same name as project folder].code-workspace in project folder

Add Extensions:

Ctrl-Shift-P : Command Line for Extensions

Java settings: Settings > Extensions > Java
   edit java.configuration.runtimes and java.home in settings.json

            "java.home": "/usr/local/java/jdk-11.0.12+7",
            "java.configuration.runtimes": [
               { 
                  "name": "JavaSE-1.8",
                  "path": "/usr/local/java/jdk8u302-b08"
               },
               { 
                  "name": "JavaSE-11",
                  "path": "/usr/local/java/jdk-11.0.12+7",
                  "default": true         
               },       
            ],
         
VSC uses the JDK/runtime where java.home is pointing to and where default=true, so settings must be changed both if a different JDK is to be used. Additionally, VSC is still sensitive towards the operating system environment, variables, so in order to change the JDK and JRE used in VSC, it ist necessary to also change the respective environemnt variables.


Java project in Visual Studio Code

VSC
The following section describes the initial generating of a new Java project using maven archetype. Since there are obviously no up-to-date archetypes, matching Java 11 or later, available, some refactoring needs do be done after generation. So it might be useful to keep the simple project, once it works, as a prototype for later projects, rather than using archetype generation again.
- Ctrl-Shift-P
- Create a Java Project
- Maven create from Archetype
- archetype-quickstart-JDK8     => this will create a basic Java project structure still suitable for JDK later than 8, however the generated pom.xml needs to be replaced.
- select a version: 2.0.0 (archetype version)
- Group ID: for example de.redeight
- Artifact ID: for example vsc_java
- Destination folder: for example /home/jade/projects (will create project folder vsc_java underneath /home/jade/projects )
    executing task: mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.2:generate
       -DarchetypeArtifactId="archetype-quickstart-jdk8"
       -DarchetypeGroupId="com.github.ngeor"
       -DarchetypeVersion="2.0.0"
       -DgroupId="de.redeight"
       -DartifactId="vsc_java"
- Interactions:
    Define value for property 'version' 1.0-SNAPSHOT: 0.1 (example)
    Package: de.redeight : Y
   [INFO] ----------------------------------------------------------------------------
   [INFO] Using following parameters for creating project from Archetype: archetype-quickstart-jdk8:2.0.0
   [INFO] ----------------------------------------------------------------------------
   [INFO] Parameter: groupId, Value: de.redeight
   [INFO] Parameter: artifactId, Value: vsc_java
   [INFO] Parameter: version, Value: 0.1
   [INFO] Parameter: package, Value: de.redeight
   [INFO] Parameter: packageInPathFormat, Value: de/redeight
   [INFO] Parameter: package, Value: de.redeight
   [INFO] Parameter: groupId, Value: de.redeight
   [INFO] Parameter: artifactId, Value: vsc_java
   [INFO] Parameter: version, Value: 0.1
   [INFO] Project created from Archetype in dir: /home/jade/projects/vsc_java
   [INFO] ------------------------------------------------------------------------
   [INFO] BUILD SUCCESS
   [INFO] ------------------------------------------------------------------------
   [INFO] Total time: 49.867 s
   [INFO] Finished at: 2021-09-20T14:24:13+02:00
   [INFO] ------------------------------------------------------------------------
   Das Terminal wird von Aufgaben wiederverwendet, drücken Sie zum Schließen eine beliebige Taste.
- File > Add folder to Workspace ... => /home/jade/projects/vsc_java
- File > Save Workspace as ... => /home/jade/projects/vsc_java/vsc_java.code-workspace

The generated project file structure looks like this:

         vsc_java/
         |-- pom.xml
         |-- src
         |   |-- main
         |   |   `-- java
         |   |       `-- de
         |   |           `-- redeight
         |   |               `-- AppSample.java
         |   `-- test
         |       `-- java
         |           `-- de
         |               `-- redeight
         |                   `-- AppSampleTest.java
         |-- target
         |   |-- classes
         |   |   `-- de
         |   |       `-- redeight
         |   |           `-- AppSample.class
         |   |-- test-classes
         |   |   `-- de
         |   |       `-- redeight
         |   |           `-- AppSampleTest.class
         |   `-- vsc_java-0.1.jar
         `-- vsc_java.code-workspace
      
- Replace the generated pom.xml to <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.redeight</groupId> <artifactId>vsc_java</artifactId> <version>0.1</version> <properties> <java.version>11</java.version> <commons.lang.version>2.6</commons.lang.version> <junit.version>5.6.0</junit.version> <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> <maven-jar-plugin.version>3.1.0</maven-jar-plugin.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>${commons.lang.version}</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <release>11</release> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>${maven-jar-plugin.version}</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>de.redeight.SampleVSCJ</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project> - Rename and refill the Java class files (AppSample.java and AppSampleTest.java) according to your needs
- Update the project: in the Explorer right-click on the project (vsc_java in this example) under JAVA PROJECTS and select Update Project from the context menu
- build: in the Explorer
    - right-click on the project (vsc_java in this example) under JAVA PROJECTS and select Run Maven Command ... from the context menu
    - select Custom and enter clean package
- run the application:
    - from VSC: use the Run code lense function at the main method
    - from terminal: java -jar target/vsc_java.jar or mvn exec:java -Dexec.mainClass="de.redeight.SampleVSCJ"
... to be continued ...


Jetty 10 (standalone) Setup

- Download Jetty 10 from here: http://www.eclipse.org/jetty/download.php
Install:
su (root)
cd /opt
cp /path/to/jetty-home-10.0.6.tar.gz .
tar -xzf jetty-home-10.0.6.tar.gz
rm ./jetty-home-10.0.6.tar.gz
mv jetty-home-10.0.6/ jetty

mkdir jetty_base
vi /etc/envvar.sh
- - -
...
export JETTY_HOME="/opt/jetty"
export JETTY_BASE="/opt/jetty_base"
- - -

- Add required modules:
cd /opt/jetty_base/
java -jar $JETTY_HOME/start.jar --add-module=server,http,deploy

- to check installed modules:
java -jar $JETTY_HOME/start.jar --list-modules=*
...

The Jetty Base structure looks now like this:

         jetty_base
         |-- resources
         |   `-- jetty-logging.properties
         |-- start.d
         |   |-- deploy.ini
         |   |-- http.ini
         |   `-- server.ini
         `-- webapps   
         
In /opt/jetty_base/start.de/http.ini change jetty.http.port to (for example) 1080 to (later on) distinguish this to other Jetty instances.

- run the Jetty server:
cd /opt/jetty_base/
java -jar $JETTY_HOME/start.jar --list-modules=*
...

... to be continued ...


Basic Servlet with Jetty 10 and Maven

- Create the file system structure:

      cd /home/jade/projects
      mkdir basicservlet
      cd basicservlet
      mkdir -p src/main/java/de/redeight
      mkdir -p src/main/webapp/WEB-INF
      touch src/main/java/de/redeight/BasicServlet.java
      touch src/main/webapp/WEB-INF/web.xml
      touch ./pom.xml
      
- the resulting file system structure:
      basicservlet/
      |-- pom.xml
      `-- src
          `-- main
              |-- java
              |   `-- de
              |       `-- redeight
              |           `-- BasicServlet.java
              `-- webapp
                  `-- WEB-INF
                      `-- web.xml
      
- BasicServlet.java:
package de.redeight; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class BasicServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().println("<h1>Hello. This is a basic servlet running on Jetty 10!</h1>"); response.getWriter().println("session=" + request.getSession(true).getId()); } } - Deployment descriptor web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" metadata-complete="false" version="3.1"> <servlet> <servlet-name>Basic Servlet on Jetty</servlet-name> <servlet-class>de.redeight.BasicServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Basic Servlet on Jetty</servlet-name> <url-pattern>/ident/*</url-pattern> </servlet-mapping> </web-app> - Maven - pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>basicservlet</artifactId> <version>0.1-SNAPSHOT</version> <packaging>war</packaging> <name>Basic Servlet on Jetty</name> <properties> <jetty.version>10.0.6</jetty.version> <servlet.version>3.1.0</servlet.version> <maven-jar-plugin.version>3.1.0</maven-jar-plugin.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <release>11</release> </configuration> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>${jetty.version}</version> </plugin> </plugins> </build> </project>


Spring Boot (web application example)

VSC
install Spring Boot Extension Pack
Ctrl-Shift-P
Spring Initializr: Create a Maven Project ...
- Spring Boot Version: 2.5.4
- Language: Java
- Input Group: de.redeight
- Input Artifact ID: sbwebsample
- Packaging Type: jar
- Java Version: 11
- Dependencies:
   - Spring Boot DevTools
   - Spring Web
- no further dependencies for now
- selected 2 dependencies > press ENTER to continue
- Dialog: Folder: ~/project ( select the (parent) project folder )
- - quit Dialog using [Generate to this folder]
- "successfully generated". Location: ~./project ==> [Open]
- [Add to Workspace]
- File > Save workspace as ... ~/project/sbwebsample/sbwebsample.code-workspace
- ... generating Java project(s) ...
- ... importing Java project(s) ... (VSC might prompt for this)

Generated project:

            sbwebsample
            |-- HELP.md
            |-- mvnw
            |-- mvnw.cmd
            |-- pom.xml
            |-- sbwebsample.code-workspace
            |-- src
            |   |-- main
            |   |   |-- java
            |   |   |   `-- de
            |   |   |       `-- redeight
            |   |   |           `-- sbwebsample
            |   |   |               `-- SbwebsampleApplication.java
            |   |   `-- resources
            |   |       |-- application.properties
            |   |       |-- static
            |   |       `-- templates
            |   `-- test
            |       `-- java
            |           `-- de
            |               `-- redeight
            |                   `-- sbwebsample
            |                       `-- SbwebsampleApplicationTests.java
            `-- target
                |-- classes
                |   |-- application.properties
                |   `-- de
                |       `-- redeight
                |           `-- sbwebsample
                |               `-- SbwebsampleApplication.class
                `-- test-classes
                    `-- de
                        `-- redeight
                            `-- sbwebsample
                                `-- SbwebsampleApplicationTests.class         
         
- Add more dependencies (if needed) - Spring Initializr: Add Starters...

Edit Code - write a REST Controller: - in the VSC Explorer / JAVA PROJECTS view select the src/main/java folder
- right-click and select New Java Class from the context menu
- class name Intro (will create package and class)
            public class Intro {

               private final long id;
               private final String content;
            
               public Intro(long id, String content) {
                  this.id = id;
                  this.content = content;
               }
            
               public long getId() {
                  return id;
               }
            
               public String getContent() {
                  return content;
               }
            }   
         
- Intro is a POJO class. Additionally a RestController class is needed
- once again right-click and select New Java Class from the context menu
- create the IntroController class. - add the @RestController annotation to the class
   - hereby use auto-completion to add the org.springframework.web.bind.annotation.RestController import
            package de.redeight.sbwebsample;

            import java.util.concurrent.atomic.AtomicLong;

            import org.springframework.web.bind.annotation.GetMapping;
            import org.springframework.web.bind.annotation.RequestParam;
            import org.springframework.web.bind.annotation.RestController;

            @RestController
            public class IntroController {
               private static final String template = "Hello, %s!";
	            private final AtomicLong counter = new AtomicLong();

               @GetMapping("/intro")
	            public Intro intro(@RequestParam(value = "name", defaultValue = "World") String name) 
               {
                  return new Intro(counter.incrementAndGet(), String.format(template, name));
               }
            }
         
- build and run the service (from VSC): use the Run code lense function at the main method:
   
- build and run the service (from the command line):
... change to the project folder run
    ./mvnw spring-boot:run
    OR
    ./mvnw clean package
    java -jar target/sbwebsample-0.0.1-SNAPSHOT.jar
- Service returns JSON format by default!

- Testing the service:
    - browse http://localhost:8080/intro => Hello, World
    - browse http://localhost:8080/intro?name=JADE => Hello, JADE

- Using embedded Jetty instead of Tomcat:
    - add the exclusion block around spring-boot-starter-tomcat
    - add the spring-boot-starter-jetty dependency
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> | <exclusions> | <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> | </exclusion> | </exclusions> </dependency> | <dependency> | <groupId>org.springframework.boot</groupId> | <artifactId>spring-boot-starter-jetty</artifactId> | </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>     - rebuild and run, that's all