# Custom JRE for spring-boot app (Java 20)

I start with a very simple spring-boot project generated by [Spring Initializr](https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.0.5&packaging=jar&jvmVersion=20&groupId=foo.bar&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=foo.bar.demo&dependencies=web).

In`pom.xml` I've got `spring-boot-starter-parent` as parent and `java.version`

```xml
<project ...>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
    <relativePath/>
  </parent>
  <groupId>foo.bar</groupId>
  <artifactId>demo-custom-jre</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
    <java.version>20</java.version>
  </properties>
...
```

Does it start? Yes!

```bash
mvn spring-boot:run
...
[INFO] --- spring-boot-maven-plugin:3.0.5:run (default-cli) @ demo-custom-jre ---
[INFO] Attaching agents: []

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.5)

INFO [ main] c.z.d.d.DemoCustomJreApplication         : Starting DemoCustomJreApplication using Java 20 with PID 58559 (/Users/ziga/git/demo-custom-jre/target/classes started by ziga in /Users/ziga/git/demo-custom-jre)
...
INFO [ main] c.z.d.d.DemoCustomJreApplication         : Started DemoCustomJreApplication in 0.664 seconds (process running for 0.829)
[CTRL-C]
```

In Spring Boot guide [https://spring.io/guides/topicals/spring-boot-docker/](https://spring.io/guides/topicals/spring-boot-docker/) thet start with a basic `Dockerfile`

```yaml
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
```

I *could* use it to build and start the app

```bash
./mvnw package
docker build --build-arg JAR_FILE=target/*.jar -t foo .
docker run -p 8080:8080 foo
```

but since I want to be on Java 20 I need a Docker Image with JDK/JRE version 20. Or I build a Custom JRE for my app.

I want to keep it simple, so everything shall be done in a Dockerfile. Multi-stage Docker build helps me to segregate steps a bit:

* compile and build the app
    
* identify the required Java modules
    
* build Custom JRE
    
* pack everything together
    

```yaml
# Build the app.jar
FROM azul/zulu-openjdk:20 as app
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD . $HOME
RUN ./mvnw package -DskipTests

# Use 'jdeps' to generate list of required modules
FROM azul/zulu-openjdk:20 as deps
WORKDIR /usr/app
COPY --from=app /usr/app/target/*.jar app.jar
RUN set -eux; \
  "$JAVA_HOME"/bin/jar -xf app.jar && \
  "$JAVA_HOME"/bin/jdeps \
  --ignore-missing-deps \
  -q \
  --multi-release 20 \
  --recursive \
  --print-module-deps \
  --class-path="./BOOT-INF/lib/*" \
  --module-path="./BOOT-INF/lib/*" \
  ./app.jar > jre-deps.info

# Build Custom JRE
FROM azul/zulu-openjdk:20 as builder
WORKDIR /usr/app
COPY --from=deps /usr/app/jre-deps.info jre-deps.info
RUN set -eux; \
  "$JAVA_HOME"/bin/jlink --verbose \
  --compress 2 \
  --strip-java-debug-attributes \
  --no-header-files \
  --no-man-pages \
  --output jre \
  --add-modules "$(cat jre-deps.info)"

# Build final image with app.jar and JRE from earlier stages
FROM ubuntu:20.04
ENV JAVA_HOME /opt/java/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
WORKDIR /usr/app
EXPOSE 8080
COPY --from=builder /usr/app/jre $JAVA_HOME
COPY --from=deps /usr/app/app.jar app.jar
ENTRYPOINT ["java","-jar", "app.jar"]
```

Quick test, does it build?

```bash
docker build -t foo .
docker run -p 8080:8080 foo
```

Yeah! My app starts. Custom JRE for my app is small, just below 60 MB.

```bash
 docker history 711bd2638b5e            
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
...
<missing>      37 minutes ago       COPY /usr/app/jre /opt/java/openjdk # buildk…   58.5MB    buildkit.dockerfile.v0
...
```

*What is the size of JDK/JRE in your Docker image?*

Thanks!

***Note: The code and configuration shown above are oversimplified and not production-ready!***
