Skip to main content
To run Java workloads in FIPS-compliant environments, you need to use FIPS-validated build and runtime images and make sure the code is correctly configured. This guide walks through the process of migrating a Java project and building it with Minimus FIPS 140-3 validated images. The guide explains the underlying concepts, image differences, and migration steps. This guide is for:
  • Application developers migrating Java services from standard OpenJDK images
  • DevOps and platform teams owning Docker and Kubernetes rollout
  • Compliance and security teams reviewing migration controls

Overview

Use Minimus images to build a FIPS compliant Java app using FIPS 140-3 validated images. These images are configured with FIPS-validated cryptographic providers and enforce strict FIPS compliance at runtime to ensure cryptographic operations are compliant with Federal Information Processing Standards. To be FIPS-compliant, every cryptographic operation (encryption, hashing, key generation, TLS) must go through a CMVP-certified provider.

Multi-stage build technique

Multi-stage builds are great for keeping the build and runtime environments separate and include only the compiled .class in the final image. The recommended process for all workloads is to build with Maven/Gradle/OpenJDK-FIPS and use OpenJRE-FIPS for the runtime stage. Java development in Minimus typically employs multi-stage Dockerfiles that separate the build/compile and runtime stages by using complementary images:
  • OpenJDK (Open Java Development Kit) is an open-source implementation of the Java Platform, Standard Edition (Java SE). Use it to compile, package, run unit tests, and any step that needs Java build tooling. It includes a Java compilerjavac and the full JDK.
  • OpenJRE (open-source Java Runtime Environment) is used to run Java applications built with OpenJDK. OpenJRE includes the JVM (Java Virtual Machine) and core libraries needed to run Java applications, without the compiler javac. OpenJRE has a smaller footprint than the OpenJDK image and only includes what is needed to run a compiled Java application.

Components

ComponentPurpose
reg.mini.dev/maven
reg.mini.dev/gradle
Build stage images
reg.mini.dev/openjdk-fipsMinimal OpenJDK image, full JDK toolset, does not
include Maven or Gradle
reg.mini.dev/openjre-fipsProduction runtime stage, leaner JRE-only image
Application JARBuild artifact copied from the build stage into the runtime stage
Optional BCFKS keystoreRequired keystore format for private keys in FIPS deployments

Environment variables

The images are pre-set with the following environment variables:
VariableValue
CLASSPATHIncludes FIPS libraries from /usr/share/fips-libs/*
JAVA_FIPS_CLASSPATHExplicit FIPS classpath reference
JAVA_HOMEPoints to the default JVM installation
JDK_JAVA_OPTIONS--add-exports=java.base/sun.security.internal.spec=ALL-UNNAMED --add-exports=java.base/sun.security.provider=ALL-UNNAMED -Djavax.net.ssl.trustStoreType=FIPS
Never overwrite JDK_JAVA_OPTIONS, CLASSPATH, or JAVA_FIPS_CLASSPATH in your Dockerfile or at runtime. These variables carry the FIPS provider wiring pre-set by the image. Always extend them by referencing the existing value, ${JDK_JAVA_OPTIONS}, rather than replacing it. Overwriting any of these will silently break FIPS compliance at runtime.

The critical java -jar problem

java -jar ignores CLASSPATH (and any -cp/--class-path you might pass), so the FIPS provider jars do not get loaded unless they are on the bootstrap classpath. Regardless of whether -Xbootclasspath/a is supplied directly in your ENTRYPOINT or injected via JDK_JAVA_OPTIONS, the CCJ provider jars must be appended to the bootstrap classpath; otherwise the JVM will have no available FIPS ciphers. The symptoms are either an empty cipher list or a NoSuchAlgorithmException at startup. The correct way to launch applications from a FIPS image is to use -Xbootclasspath/a to append the FIPS jars directly to the bootstrap classloader, which is not bypassed by java -jar:
ENTRYPOINT ["java", "-Xbootclasspath/a:/usr/share/fips-libs/*", "-jar", "/app/app.jar"]
The wildcard /usr/share/fips-libs/* loads all jars in that directory. Do not hardcode specific jar filenames, as the version numbers change as the image updates. Pinning the version numbers will cause silent classpath failures.

FIPS approved algorithms

FIPS approved-only mode (com.safelogic.cryptocomply.fips.approved_only=true) enforces a hard algorithm blocklist. Your application will throw a NoSuchAlgorithmException or GeneralSecurityException at runtime - not at compile time - if it calls any of the blocked algorithms. Below is a table showing the recommended migration paths:
Algorithms blocked by FIPSFIPS-approved replacement algorithm
MD5 (for any security purpose)SHA-256 or SHA-3
SHA-1 signaturesSHA-256 or stronger
DES / 3DESAES-128 or AES-256
RC4AES-GCM
TLS 1.0 / TLS 1.1TLS 1.2 or TLS 1.3
RSA or DH keys under 2048 bitsRSA-2048 minimum, RSA-3072 preferred
PKCS#12 for private key storageBCFKS keystore format
To migrate your project to FIPS mode, you will need to audit your codebase, and search for string literals like "MD5", "SHA1", "DES", "RC4", "TLSv1", and "PKCS12" in any getInstance() or KeyStore.getInstance() calls.

How to deploy Java with the Minimus OpenJRE-FIPS image

Prerequisites

  • Docker or Podman available locally
  • Token to pull images from the Minimus image registry
  • Existing Java project (Maven or Gradle)
  • A working test environment for smoke tests and crypto-related checks
  • Host with FIPS-enabled kernel as listed in the CMVP certificate
Minimus Java FIPS images use a kernel-dependent FIPS module. Unlike Minimus OpenSSL-based FIPS images, Java FIPS images require a FIPS-enabled kernel and specialized hardware as listed in CMVP certificate #4912. Verify your target environment meets these requirements before deploying. To check whether a specific Minimus image includes the Java FIPS module, look for the package minimus-java-fips-libs in the image SBOM.

Step 1: Pre-flight modifications

1

Update your application to use only FIPS-approved algorithms

The first step is to audit your application for non-FIPS algorithm usage. Before changing your Dockerfile, scan your codebase for algorithm strings that FIPS will reject at runtime.Common offenders are MD5, SHA1, SHA-1, DES, RC4, TLSv1, TLS1.0, TLS1.1, and PKCS12 used as a keystore type for private keys.Replace any incompatible algorithms found with FIPS-approved equivalents and ensure TLS configuration specifies a minimum of TLSv1.2. See the list of approved algorithmsRun your existing test suite before proceeding.
2

Update your Dockerfile

Update your Dockerfile to use Minimus images:
  1. Use a build image that includes your build tooling (Maven or Gradle).  We need to use Maven or Gradle because the Minimus OpenJDK-FIPS image does not include Maven or Gradle by default. 
The Minimus OpenJDK-FIPS image is a minimal JDK image that does not include Maven or Gradle by default. Therefore it cannot be used for the build stage.
You can use the OpenJDK-FIPS image for the build stage if you install Maven in a prior step or supply your own build tool, such as a Maven Wrapper.
  1. Copy the produced JAR into the OpenJRE-FIPS image for the runtime stage.
  2. Make sure the ENTRYPOINT includes -Xbootclasspath/a:/usr/share/fips-libs/* to load the CCJ FIPS provider. Without this flag, java -jar bypasses the image’s pre-set CLASSPATH (and any -cp/--class-path you might pass), so the FIPS provider jars are never loaded into the bootstrap classloader and the JVM will have no available FIPS ciphers.
  3. Use a wildcard such as /usr/share/fips-libs/* to load all CryptoComply jars in that directory. Avoid hardcoding specific jar filenames such as ccj-4.0.0-fips.jar since the version numbers change as the image updates. Pinning the version numbers will cause classpath failures silently and should be avoided.
  4. Set a Main-Class to avoid errors. If your build produces a non-executable JAR (no Main-Class manifest entry), java -jar /app/app.jar will fail with the error: no main manifest attribute. Ways to fix this issue:
    • Configure Maven or Gradle to produce an executable JAR (set Main-Class)
    • Run the app with an explicit main class, e.g. java -cp /app/app.jar com.example.App.

Step 2: Deploy your Java project

1

Prepare your Dockerfile

You can use the below example Dockerfile with your Java project. 
# Use the Minimus Maven image for the build stage

FROM reg.mini.dev/maven:latest AS builder
WORKDIR /workspace

COPY pom.xml ./
COPY src ./src

RUN mvn -DskipTests package

FROM reg.mini.dev/openjre-fips:21
WORKDIR /app

COPY --from=builder /workspace/target/*.jar /app/app.jar

# Extend existing options, do not replace them

ENV JDK_JAVA_OPTIONS="${JDK_JAVA_OPTIONS} -XX:+ExitOnOutOfMemoryError"

ENTRYPOINT ["java", "-Xbootclasspath/a:/usr/share/fips-libs/*", "-jar", "/app/app.jar"]
2

Validate that image roles are correct

Confirm the build image (openjdk-fips) has compiler access:
Confirm
docker run --rm reg.mini.dev/openjdk-fips:21 javac -version
Confirm the runtime image (openjre-fips) is JRE-only:
Confirm
docker run --rm reg.mini.dev/openjre-fips:21 java -version
If any step in your Dockerfile requires javac or other JDK tools, it belongs in the openjdk-fips build stage. The openjre-fips runtime stage should only execute the already-compiled artifact.
3

Build Java FIPS app

Build the image from your Dockerfile:
Build
docker build -t myapp-fips:latest .
4

Run Java FIPS app

Run the application:
Run
docker run --rm -p 8080:8080 myapp-fips:latest

Step 3: Verify your app

1

Verify FIPS provider loading

Save the following code as TestFIPS.java . We will run it against the image to confirm the CCJ and BCJSSE providers are loaded at the correct positions and that non-FIPS algorithms are blocked:
TestFIPS.java
import java.security.Provider;
import java.security.Security;

public class TestFIPS {
    public static void main(String[] args) {
        System.out.println("=== FIPS Compliance Test ===");

        String approvedOnly = Security.getProperty("com.safelogic.cryptocomply.fips.approved_only");
        boolean isApprovedOnly = approvedOnly != null && approvedOnly.equals("true");

        if (!isApprovedOnly) {
            System.err.println("[ERROR] SafeLogic CryptoComply FIPS Approved Only Mode is disabled!");
            System.exit(1);
        }

        Provider[] providers = Security.getProviders();
        boolean foundCryptoComply = false;
        boolean foundBCJSSE = false;
        int cryptoComplyPosition = -1;
        int bcjssePosition = -1;

        for (int i = 0; i < providers.length; i++) {
            String name = providers[i].getName();
            if (name.contains("CCJ") || name.contains("CryptoComply")) {
                foundCryptoComply = true;
                cryptoComplyPosition = i + 1;
                System.out.println("[OK] SafeLogic CryptoComply provider found at position " + cryptoComplyPosition);
            }
            if (name.contains("BouncyCastleJsse") || name.contains("BCJSSE")) {
                foundBCJSSE = true;
                bcjssePosition = i + 1;
                System.out.println("[OK] Bouncy Castle JSSE provider found at position " + bcjssePosition);
            }
        }

        if (!foundCryptoComply) { System.err.println("[ERROR] CCJ provider NOT found!"); System.exit(1); }
        if (!foundBCJSSE) { System.err.println("[ERROR] BCJSSE provider NOT found!"); System.exit(1); }
        if (cryptoComplyPosition != 1) System.err.println("[WARNING] CCJ should be at position 1");
        if (bcjssePosition != 2) System.err.println("[WARNING] BCJSSE should be at position 2");

        try {
            javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding", "CCJ");
            System.out.println("[OK] AES algorithm available");
            java.security.MessageDigest.getInstance("SHA-256", "CCJ");
            System.out.println("[OK] SHA-256 algorithm available");
            java.security.KeyPairGenerator.getInstance("RSA", "CCJ");
            System.out.println("[OK] RSA algorithm available");

            try {
                java.security.MessageDigest.getInstance("MD5", "CCJ");
                System.err.println("[ERROR] MD5 is available — FIPS mode not fully enforced!");
                System.exit(1);
            } catch (Exception e) {
                System.out.println("[OK] MD5 correctly blocked: " + e.getMessage());
            }

            System.out.println("=== FIPS Compliance Test PASSED ===");
        } catch (Exception e) {
            System.err.println("[ERROR] FIPS algorithm test failed: " + e.getMessage());
            System.exit(1);
        }
    }
}
Compile and run:
Run
docker run --rm -v $(pwd):/home/build reg.mini.dev/openjdk-fips:21 sh -c \
  "javac /home/build/TestFIPS.java -d /home/build && java -cp /home/build TestFIPS"
Expected output:
Expected
=== FIPS Compliance Test ===
[OK] SafeLogic CryptoComply provider found at position 1
[OK] Bouncy Castle JSSE provider found at position 2
[OK] AES algorithm available
[OK] SHA-256 algorithm available
[OK] RSA algorithm available
[OK] MD5 correctly blocked: ...
=== FIPS Compliance Test PASSED ===
If TLS connections return an empty cipher list or throw NoSuchAlgorithmException, the CCJ provider is not being registered. Check that -Xbootclasspath/a:/usr/share/fips-libs/* is present in your ENTRYPOINT.
2

Run smoke tests in the new image

Validate the following application paths:
  • Service startup
  • Health endpoints
  • TLS client and server connections
  • Authentication, token signing, and password hashing paths
  • Any code paths that invoke cryptographic operations directly
For deep provider-level and algorithm-level validation, see Java FIPS Validated Module.

Step 4: Roll out to Kubernetes

1

Create a Minimus registry pull secret

Create a pull secret for the Minimus registry (first update the command with your Minimus token and the relevant namespace):
Create
kubectl create secret docker-registry minimus-registry \
  --docker-server=reg.mini.dev \
  --docker-username=minimus \
  --docker-password={token} \
  -n my-namespace
2

Update your deployment YAML and deploy

Update your Deployment to reference openjre-fips as the runtime image and add the imagePullSecrets:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      imagePullSecrets:
        - name: minimus-registry
      containers:
        - name: myapp
          image: reg.mini.dev/openjre-fips:21
          ports:
            - containerPort: 8080
Apply the rollout and verify:
Apply
kubectl apply -f deployment.yaml -n my-namespace
kubectl rollout status deployment/myapp -n my-namespace
kubectl logs deploy/myapp -n my-namespace --tail=200
3

Handle keystore requirements

FIPS mode restricts the keystore formats allowed for private key storage. For example, FIPS mode bans the standard JKS and PKCS12 formats. The Bouncy Castle FIPS KeyStore format (BCFKS) is required instead.
Use caseAllowed formats
Storing private keysbcfks only
Truststores (public CA certs only)jks, pkcs12, or bcfks
If your team needs a complete keystore and certificate generation workflow — including keytool commands for BCFKS keystore and truststore creation, CA generation, and certificate signing — see the Keycloak FIPS Tutorial. The keytool patterns shown there apply directly to any Java application using Minimus FIPS images, not just Keycloak.
If your application currently loads a .jks or .p12 file containing a private key at startup and passes it to an SSLContext, that will throw a KeyStoreException at runtime in FIPS mode. The keystore must be regenerated in bcfks format before deploying.
FIPS mode enforces a minimum password length of 14 characters (112 bits) for all BCFKS keystores and truststores. Passwords shorter than this will be rejected with the error password must be at least 112 bits. Use passwords of 16–24 characters.

Troubleshooting

IssueLikely causeAction
Empty cipher list at startupCCJ provider not registered — java -jar bypassed CLASSPATHAdd -Xbootclasspath/a:/usr/share/fips-libs/* to ENTRYPOINT
NoSuchAlgorithmException: RSA KeyFactoryFIPS provider not loadedAdd -Xbootclasspath/a:/usr/share/fips-libs/* to ENTRYPOINT
Runtime algorithm errorsLegacy non-approved algorithm in useReplace with FIPS-approved algorithms and rerun tests
App works in build stage but not runtimeBuild-only tools expected at runtimeMove compile steps to the openjdk-fips stage only
Unexpected Java option behaviorDefault Java options overwrittenExtend JDK_JAVA_OPTIONS using ${JDK_JAVA_OPTIONS}
Keystore loading errorsPrivate-key store format mismatchConvert private key keystores to BCFKS
Password rejected at keystore creationPassword under 14 charactersUse a password of 16–24 characters (minimum 112 bits)
Cluster pull failuresMissing or invalid registry secretRecreate pull secret and verify namespace binding

Last modified on April 16, 2026