- ⚠️ Java apps in Docker can use too much memory if
JAVA_OPTSis 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_OPTSneed 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.
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_OPTSinto thejavacommand. - 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_OPTSin Dockerfile but not using it in theCMD - ⚠️ Using tricky shell entrypoints without making the final command run correctly
- 🙈 Using
docker-compose.ymlwithout giving it needed variables or changing thecommand - ⚒️ 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_OPTSinstead - 🔥 Tomcat: Use
CATALINA_OPTSfor server settings - 🚀 Spring Boot Fat JARs: Set options right in wrapper scripts or the
CMDline - ⚙️ 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
-Xmsand-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
javaprocess (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_OPTSused by thejavacommand? - ✅ Is your
ENTRYPOINTorCMDmade 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:
- 🔮 Java 17+ better sees cgroup v2 containers and memory limits.
- 📝 Look for new JEPs like:
- 🔄 Review your scripts and images often. Base images often add changes that break things or new default settings.
- 🧪 Use tests that check Java memory startup settings after the container is built.
Key Takeaways
- The JVM does not automatically use Docker memory limits unless you set it up.
JAVA_OPTSis just a variable. It must be clearly used in entry scripts or commands.- For newer setups, use
-XX:MaxRAMPercentageinstead of fixed-Xmxvalues. - 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/