Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

JAVA_OPTS in Docker: Why Isn’t It Working?

Discover why your Docker container isn’t using the passed-in JAVA_OPTS. Learn JVM heap behavior and Docker memory allocation.
Developer confused by Java app crashing inside Docker container due to ignored JAVA_OPTS and JVM memory issues Developer confused by Java app crashing inside Docker container due to ignored JAVA_OPTS and JVM memory issues
  • ⚠️ Java apps in Docker can use too much memory if JAVA_OPTS is not directly given to the container.
  • ⚙️ Before Java 10, JVMs did not know about container memory limits. They used information from the host computer, and this caused out-of-memory problems.
  • 📦 Memory settings made for containers, like -XX:MaxRAMPercentage, only work if your JVM and container setup work with them completely.
  • 🔍 Environment variables like JAVA_OPTS need to be included in entrypoint scripts to change how the Java process works.

JAVA_OPTS in Docker: Why Isn’t It Working?

If your Java app does not work right inside a Docker container—it crashes, gives out-of-memory (OOM) errors, or does not use heap settings—many people face this. A common reason is JAVA_OPTS that are wrongly set up or not used. Java usually works well on your own computer. But Docker adds new limits that stop the JVM from finding system resources and using setup options the right way. In this guide, we will look closely at why JAVA_OPTS may not be working as it should. We will also see how Docker Java memory settings are different from those on a physical computer or virtual machine. And then we will look at ways to set up Java applications running in Docker containers correctly.


Understanding JAVA_OPTS in Typical Environments

JAVA_OPTS is a common way to put options into the Java Virtual Machine (JVM). Developers and system administrators use it to change how programs run, how much memory is used, system settings, file encoding, options to make things faster, and other things. Some common settings are:

JAVA_OPTS="-Xmx512m -Xms256m -Dfile.encoding=UTF-8 -XX:+UseG1GC"

This variable is very helpful when running apps in different places (like staging or production) without changing the code itself. But it is good to know that JAVA_OPTS is a common practice, but the JVM does not make you use it. It only works if the startup script, container image, or entrypoint uses it when starting up.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Other variables like this are:

  • _JAVA_OPTIONS: The JVM uses this automatically no matter how the Java process starts.
  • CATALINA_OPTS: You use this only in Apache Tomcat to control specific server settings, not general ones.

In older setups (like on your own computer or physical servers), using JAVA_OPTS is usually simple. But once you put your Java application into a container, especially with Docker, things do not work the same, and problems appear. This is mostly because of how the JVM works with resource limits in containers.


Why JAVA_OPTS Gets Ignored in Docker Containers

When working with Docker setups, many developers think that variables you set in a Dockerfile or with docker run -e will make the java command work a certain way inside the container. But this often leads to surprises.

Here are the main reasons your JAVA_OPTS might not be used:

❌ Missing Integration in ENTRYPOINT or CMD

Docker containers set how they run using CMD or ENTRYPOINT. Unless these directly use and add $JAVA_OPTS to the java command, the JVM will start without your memory or speed options.

🔄 Shell vs Exec Scripts

Some container images use shell scripts like start.sh. If not done right, these scripts do not use variables unless you clearly show them or give them to the final exec command.

🪵 ENV Declarations Are Not Enough

Saying ENV JAVA_OPTS="-Xmx512m" in your Dockerfile does almost nothing. This is true unless JAVA_OPTS is connected to how the java process starts and runs. Just setting the variable only changes the shell, not the command that finally runs your app.

🧱 Docker Image Defaults

Many base images already have their own ENTRYPOINT or CMD. Changing just one without knowing how the image works can stop variables from being passed in.

✅ TL;DR: Use Entry Scripts that Adopt JAVA_OPTS

Make sure the image you use:

  • Has a startup script that puts $JAVA_OPTS into the java command.
  • Or, has a CMD or ENTRYPOINT that you can change to include your JVM options directly.

JVM and Docker Memory Allocation: The Myth

A common wrong idea is that setting Docker’s memory limits automatically limits how much memory Java apps will use.

Here is the truth:

  • Before Java 10, the JVM got memory information from the host OS. It did not care about Docker container memory limits.

  • This meant that even if you set a top limit for your Docker memory with --memory="512m", the JVM inside could try to use 4GB. This caused crashes and out-of-memory errors right away.

  • After Java 10, the JVM got better at seeing cgroups, so you could set it up. But it still needs you to choose to use specific JVM memory options like -XX:MaxRAMPercentage.

💡 As per the OpenJDK Docker documentation, Java 10+ has default container awareness. But this may not work well with all versions and systems.

The confusion about container memory limits and what the JVM heap can actually use is one of the main reasons Java memory acts strange in Docker.


JVM Container Awareness: Understanding How It Works

Java’s idea of a “system” changes a lot inside Docker because of Linux cgroup limits and namespaces. For Java applications to work right with those limits, you need more than Docker memory limits. The JVM also needs to work with them.

✅ Container-Aware Flags in Java

Starting with Java 10:

  • The JVM finds container limits by default using cgroup v1/v2.
  • The JVM uses these limits only if container support is turned on—either by default or with options.

Java 8 Users: Extra Caution

If you are still using Java 8, make sure you are on 8u191 or later. Older versions do not see cgroups at all. Even in 8u191+, container support is NOT turned on by default. You must add:

-XX:+UseContainerSupport

Important Container-Specific Options

-XX:+UseContainerSupport          # Required in Java 8u191+
-XX:MaxRAMPercentage=75.0         # Best memory limit for newer JVMs
-XX:InitialRAMPercentage=50.0     # Starting heap size compared to container memory

These options let you change how things work based on percentages of container memory. They do not use fixed amounts of host memory. This is a big change for Java developers used to physical computer setups.


Setting Java Memory Options Correctly in Docker

To make sure your memory options are used correctly, pick one or more ways to put JVM options right into the Java process:

✅ Set Memory Options Clearly

Use one of these ways:

-Xms512m -Xmx512m

or settings that can change:

-XX:MaxRAMPercentage=75.0
-XX:InitialRAMPercentage=25.0

These memory options help make things faster while keeping within Docker limits.

✅ Put Options Where the JVM Starts

Dockerfile Example

ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0"

Just saying this is not enough. See below for where it runs.

Entrypoint Script Example

#!/bin/sh
exec java $JAVA_OPTS -jar yourapp.jar

Make sure this script is the container’s ENTRYPOINT:

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Docker Run Example

docker run -e JAVA_OPTS="-Xmx512m" your-image

Using _JAVA_OPTIONS

JVMs automatically see this variable:

ENV _JAVA_OPTIONS="-Xmx512m"

But other images might change or remove it, so check if it works.


Common Mistakes to Avoid

  • 🚫 Setting JAVA_OPTS in Dockerfile but not using it in the CMD
  • ⚠️ Using tricky shell entrypoints without making the final command run correctly
  • 🙈 Using docker-compose.yml without giving it needed variables or changing the command
  • ⚒️ Using base images like Amazon Corretto or Eclipse Temurin, which might have hidden actions or special entrypoints
  • 🔄 Trusting defaults instead of checking if your options are used when running

Each container image has its own oddities. Read the Dockerfile and startup scripts. Do not assume anything.


Debugging a Container Ignoring JAVA_OPTS

Let us look at a real problem and how to fix it.

❌ Problem Dockerfile

ENV JAVA_OPTS="-Xmx512m"
CMD ["java", "-jar", "app.jar"]

Here, JAVA_OPTS is not used at all in the run command.

✅ Fixed Version Using Entrypoint Script

entrypoint.sh

#!/bin/sh
exec java $JAVA_OPTS -jar app.jar

Dockerfile

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
ENV JAVA_OPTS="-Xmx512m"

Now, $JAVA_OPTS is put cleanly into the final command.


Verifying JVM Memory Settings in a Docker Container

How do we know our Docker container JVM settings are really used?

🔍 From Inside the Container

echo $JAVA_OPTS
ps aux | grep java

🔧 Using Diagnostic Tools

jcmd <pid> VM.info

Or check heap setup in code:

System.out.println("Max Heap Size: " + Runtime.getRuntime().maxMemory());

If you expect 512MB but see a different value, your options were not used right.


When Not to Use JAVA_OPTS

In some Java tools, using JAVA_OPTS is not advised or does not matter:

  • 🧪 Maven: Use MAVEN_OPTS instead
  • 🔥 Tomcat: Use CATALINA_OPTS for server settings
  • 🚀 Spring Boot Fat JARs: Set options right in wrapper scripts or the CMD line
  • ⚙️ Kubernetes setups: Think about using resource limits and JVM options that change, like -XX:MaxRAMPercentage

If building your own container from scratch, use exact JVM options in the startup command for more predictable results.


Good Ways to Make Dockerized Java Apps Stable

  • 🏗️ Set a minimum and maximum for your heap using -Xms and -Xmx, especially in places with limited memory.
  • 📈 Use percentage-based sizing for containers that scale on their own.
  • ✅ Check that options are in the actual java process (ps, jcmd, or logs).
  • 🧹 Use new garbage collection settings and tools to check container performance.
  • 🧪 Keep versions updated. Java 17+ has better default settings for containers.

Checklist for Fixing Problems

  • ✅ Is JAVA_OPTS used by the java command?
  • ✅ Is your ENTRYPOINT or CMD made to pass changing variables?
  • ✅ Are you using a JVM that knows about containers (Java 10+, or Java 8 with +UseContainerSupport)?
  • ✅ Did you check heap size while the program was running?

Make it simple until you are sure the heap size is what you expect. Then add more complex parts.


Future-Proofing Your Setup

Containers and Java keep getting better together:


Key Takeaways

  • The JVM does not automatically use Docker memory limits unless you set it up.
  • JAVA_OPTS is just a variable. It must be clearly used in entry scripts or commands.
  • For newer setups, use -XX:MaxRAMPercentage instead of fixed -Xmx values.
  • Check during runtime to make sure memory settings are used.
  • Keep your base images, JVM versions, and deployment scripts updated so they work well with containers.

If this guide helped you fix your Java container memory problems or make JAVA_OPTS debugging easier, you might save it for later use by your team or on other projects.


References

Azul Systems. (2023). What’s New in Java 17 in Containers. Retrieved from https://www.azul.com/blog/whats-new-in-java-17-in-containers/

OpenJDK. (2018). JEP 8187849: Support Container Memory Limits in HotSpot. Retrieved from https://openjdk.org/jeps/8187849

Docker Inc. (n.d.). Best practices for writing Dockerfiles. Retrieved from https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading