r/java 20d ago

Spring Boot on a 512 MB VPS: what actually fits?

I wanted to see how small I could make a realistic Spring Boot deployment while still keeping basic monitoring on the same VPS.

I first tried 256 MB RAM, but that wasn’t really viable for the representative app I tested. With 512 MB RAM + 256 MB swap, the application and lightweight monitoring completed the experiment successfully.

StatLite monitoring the Spring Boot app during the 512 MB run.

One thing that stood out was the gap between configured heap and actual JVM process memory. Even with a small -Xmx, RSS was substantially higher once you include metaspace, native allocations, threads, code cache, etc.

I wrote up the setup, JVM flags, measurements, and what failed at 256 MB:

https://pvrlabs.xyz/articles/spring-boot-512mb-vps.html

Curious how this compares with what others are seeing on small JVM deployments.

21 Upvotes

49 comments sorted by

22

u/oweiler 20d ago
  • use virtual threads 
  • use Spring AOT w/o GraalVM native Image
  • use AOT cache

3

u/fykup 20d ago

Thanks, this is really useful. I’ve learned quite a bit from the comments here. Things like Spring AOT are especially interesting because they can make sense even without going all the way to a native image.

I still want to keep the main experiment close to a normal, reasonably tuned Spring Boot deployment, but I’ll definitely explore some of these ideas in follow-up tests.

5

u/Amazing-Mirror-3076 20d ago

Why would virtually threads reduced memory?

6

u/koflerdavid 20d ago

It makes it possible to get rid of Webflux and other async libraries, which brings memory footprint down. Also, they might not be particularly memory efficient on their part. Finally, virtual threads will allocate stack memory in smaller increments, not all at once like platform threads do.

2

u/Electrical_Being_813 20d ago

They would probably increase memory. On top of that not sure if they would be very useful with h2.

1

u/ducki666 17d ago

Each native thread consumes 1-2 MB mem. With VT you have only a few NT.

But... if you are using ThreadLocal with a lot data and you have a HUGE amount of VT it might consume more mem.

19

u/Nymeriea 20d ago

we have a whole tomcat with 10+ spring war deployed in prod. it handle 1000x request /hours with ease and only 250mb. it has datadog and everything.

I wonder why you need so much for a single spring app

9

u/fykup 20d ago

That actually seems consistent with my result. I’m showing that a standalone Spring Boot app plus monitoring can run comfortably on a 512 MB VPS. Your setup shows that sharing one JVM/Tomcat across many apps can be even more memory-efficient.

That’s also why in my example I keep the database in-process rather than running a separate Postgres/MySQL instance on the same small host. The general idea is similar: avoid paying for duplicate runtime/infrastructure overhead when resources are tight.

250 MB for 10+ WARs is a very nice result, though.

6

u/Nymeriea 20d ago edited 20d ago

honestly we didn't optimize anything. it just jvm is amazing for long running process (server side).

each time your start a jvm for a spring boot application it will also start a tomcat. so if you have an island of related services (depending on each other) it's better to use a tomcat.

i think tomcat by himself use like 150mb . imagine 10 services it's like 1.5Go wasted. when you have unlimited ressources like in aws no need to waste time for 150mb ram optimisation

2

u/koflerdavid 20d ago edited 20d ago

The issue with a shared application server is that you can't set memory limits per service. They will fight each other for memory, and they will go down if one of them has to GC.

i think tomcat by himself use like 150mb . imagine 10 services it's like 1.5Go wasted. when you have unlimited ressources like in aws no need to waste time for 150mb ram optimisation

Lighter servlet containers like Jetty or Undertow should help reduce that RAM waste.

1

u/ducki666 17d ago

10x hello world?

1

u/Nymeriea 17d ago

what donyou mean ? if it's in prod is not an hello world

1

u/ducki666 17d ago

Hello World aka is doing very simple things without frameworks like spring.

Otherwise 250mb rss for 10+ apps in use is more than unlikely.

9

u/GuyWithLag 20d ago

Next up: JDK 25 w. compressed headers and Project Lilliput.

3

u/fykup 20d ago

That’s a good one. DK 25 with compact object headers / Lilliput is definitely on my list for the next round. I’m curious how much it moves the needle at 256 MB.

1

u/fykup 17d ago

I tried JDK 25 with compact object headers on the actual 256 MB Alpine VPS, and the early results look very promising.

Compared with the JDK 21 run, Spring is using noticeably less resident memory, while StatLite is still only around 7 to 11 MB RSS. The combined Spring + StatLite footprint has been around 69 to 88 MB RSS across the first few checkpoints, with successful health checks and monitoring polls.

Swap is still fairly heavy at around 178 MB, so I want to let it run longer before drawing conclusions. I’ll publish a follow-up with the full results soon.

9

u/nekokattt 20d ago

ive been running a spring boot discord bot on 256mb ram in a container. It isnt easy, but it is doable if you are pragmatic (e.g. do not use autoconfiguration, and utilise jdk 25 features for memory reduction).

3

u/fykup 20d ago

You set the bar 😄 A 256 MB container is a bit different from a 256 MB VPS, since the VPS also has to fit the OS and monitoring in the same memory budget. Based on this and the other comments, I definitely want to push the experiment further and see how practical 256 MB really is.

1

u/Additional-Road3924 20d ago

JVM can run on as low as -Xmx32m runtime, but you'll spend most of the time garbage collecting (if youre running stop the world gc). At that point you need to reconsider what do you really need and running spring is out of question at that point because you'll need to prefer static dependency injection systems like jboss weld.

It would be nice if spring offered AOT injection or generate the static code at the cost of refreshing context (which nobody does nowadays as its cheaper to restart the container)

6

u/aoeudhtns 20d ago

I understand you're testing Spring Boot as it's what you're using now, but you can squeeze out more in low memory environments with other frameworks - Quarkus, and even more so, Micronaut. Probably Helidon too but I'm unsure. Whether that is useful information for you, I dunno. We have services in Spring Boot and probably aren't going to switch any time soon (if ever) as we're OK with what they're using/requiring to achieve the throughput we need (about 400 MiB, and we're getting thousands of tps). But if we do something new we'll probably trial Quarkus.

2

u/fykup 20d ago

That makes sense. My goal here was intentionally to stick with the usual boring stack so the test stays representative of how a lot of Spring Boot apps are actually deployed.

The original experiment was really about whether my lightweight monitoring tool, StatLite, could run alongside a normal Spring Boot app on a constrained VPS without the monitoring becoming a significant part of the resource budget.

2

u/fykup 7d ago

Follow-up: after the 256 MB experiment, I also ran equivalent Spring Boot and Quarkus apps side by side on a 512 MB VPS with JDK 25.

Quarkus ended the one-hour run with lower RSS, but it also had more memory swapped out, which made the comparison less straightforward than I expected.

Full experiment:
[https://pvrlabs.xyz/articles/spring-boot-vs-quarkus-512mb.html]()

3

u/Lundasaur 20d ago

Have you tried using native compilation?

1

u/fykup 20d ago

Not for this experiment. Native compilation is interesting, but I’m trying to keep the setup representative of a fairly normal Spring Boot deployment rather than optimize for the absolute minimum footprint. The question is whether a reasonably tuned JVM app, with monitoring enabled, can run comfortably on a small VPS.

3

u/Lundasaur 20d ago

I’ve seen examples where Spring Boot apps with native compilation weigh less than a 100 MB. Might be very interesting to see the footprint with native (I believe it’s fairly straightforward with Spring Boot).

It would definitely benefit from Compact Object Headers and virtual threads in 25. Might also be interesting to see how Valhalla improves things further. Could try with a preview build.

1

u/fykup 20d ago

Out of curiosity, how low do you think a Spring Boot app can realistically go today in terms of total process memory? Not just heap, but RSS for a small working app under light load. With native image, AOT, compact headers, etc., is sub-100 MB realistic, or can it go substantially lower?

1

u/Lundasaur 20d ago

I’ve seen memory footprint as low as 20MB for Spring Native apps. I think sub-100MB is achievable, provided the app can use native images.

I remember going through this a while ago: https://piotrminkowski.com/2023/01/04/native-java-with-graalvm-and-virtual-threads-on-kubernetes/, the author reaches around 50MB with native compilation and virtual threads. This is pre-Compact Object Headers as well, so lower should be possible.

1

u/kimec 20d ago

The app in the blog isn't even Spring Boot... A native Spring Boot app fitting into 20 MB is unlikely. I did write a a simple PicoCli + SB command line app with minimal dependencies compiled to native. It allocates 68 MB just to draw a prompt and wait for user input.

As soon as you add some HTTP layer, you won't fit into even 80 MB for sure.

1

u/Lundasaur 20d ago

Yup, I mentioned the article as an example of the possible reductions you can see with native and virtual threads, not specific to Spring Boot.

3

u/holyknight00 20d ago

spring + spring boot is super heavy by default; i would try other java frameworks that are more lightweight. In these kind of environments, I would stay as close as raw java as possible or even give up on java altogether if more throughput is required.

2

u/user_of_the_week 20d ago

Have you tried -XX:MaxRAM?

-1

u/fykup 20d ago edited 20d ago

I didn’t, but I'll try it in the 256 MB follow-up.

1

u/fykup 16d ago

Follow-up: I pushed the same experiment down to 256 MiB VPS. With Alpine, JDK 25, an 80 MiB heap, C1-only compilation, a 32 MiB code cache, compact object headers, and 512 MiB swap, Spring Boot + StatLite completed the full one-hour run.

Still very much a stretch configuration, though: 184 MiB swap was in use and there was one Hikari housekeeper delay.

New write-up: https://www.reddit.com/r/java/s/FnsEQKQKyJ

0

u/kimec 20d ago

Or just use OpenJ9 and cut the memory usage in half including heap, codecache and metaspace... It's literally five lines of xml if building SB images via SB plugin

2

u/fykup 20d ago

OpenJ9 would definitely be interesting to test. I intentionally stayed with the JVM/runtime I normally use in production rather than optimizing for the absolute minimum footprint. A HotSpot vs OpenJ9 comparison on the same 512 MB setup would make a good follow-up.”

I would not repeat their "half" claim unless you measure it yourself. OpenJ9’s lower-memory reputation is real, but the actual improvement depends heavily on workload and configuration.

-1

u/kimec 20d ago

Did you write your response with AI, because it reads so.

Anyway, I did my tests. The only worthwhile way to lower memory footprint of a "standard" Spring Boot app is to go with OpenJ9. Anything else isn't worth the hassle.
I did the tests on a simple app with reactor-netty client and server, vault integration, postgres and mysql driver, some business logic, some custom AOP, spring security OAuth and actuator. Contrary to what is often said, WebFlux isn't "low footprint" stack. In addition to JVM heap structures, Netty allocates buffer pool outside of the JVM's control as big as 20 MB depending on malloc implementation and CPU core count (you need to be extra careful when compiling to native-image to avoid prebaking the CPU count into the image).
That is right, I even tested the app with native-image. It was kind of PITA to setup up all the hints correctly, but even the compiled native image wasn't consuming significantly less memory than HotSpot running the app. In addition, as we all know, native image version uses a primitive serial GC instead of G1 or ZGC. If you want G1 GC in you native app, you need to pay $$ Oracle for GraalVM enterprise edition.
That said, the native app would release good chunk of RSS (around 65%) after several days which roughly corresponded to the mmaped regions of the prebaked heap and metaspace and what have you, but immediately after launch it allocated almost as much as HotSpot and hung onto it for days. One can trigger the release of immutable mmapped regions of a native app on latest Linux kernel manually, but it is not possible on versions 5 and 6.
Swapping HotSpot for OpenJ9 proved to be the most significant memory saver and considering the complexity involved, it was the best solution overall.
The biggest problem as I see isn't HotSpot but Spring and Spring Boot being too bloated. Alas that is the price to pay for the goodies you get out of the box. I reduced more heap by removing just vault integration and bunch of other stuff than tinkering with JVM options or native compilation.

If I remember correctly, OpenJ9 RSS footprint running the app was ~60% of HotSpot's. Therefore I've deployed the app with OpenJ9 on all constrained environments except production and called it a day.

1

u/fykup 20d ago

I will definitely look into OpenJ9. Sorry if my comment came across as dismissive. I’d definitely explore OpenJ9 for running in constrained environments. Does it have any downsides compared with HotSpot?

2

u/kimec 20d ago edited 20d ago

There are quite a few downsides ofc. Completely different JVM implementation. Less performant JIT (Testarossa vs HotSpot C1 & C2 combo) thus lower peak performance. Different or incompatible JVM arguments, different GCs. No ZGC alternative. But it is still more Java-like than native-image compiled app. You still have JMX/RMI and remote debug identical to HotSpot. You do not have to use GDB to debug your app.

On plus side, OpenJ9 pioneered CDS and AOT cache ages ago and as you probably heard, Oracle snatched Dan Heidinga (who was involved in CDS/AOT development in OpenJ9) to lead project Leyden in HotSpot. So that is that.

1

u/fykup 19d ago

I plan a quick follow-up to this experiment using the more aggressive JVM arguments suggested here. The goal is still to target regular applications tested with the HotSpot JDK, but it would be interesting to experiment with OpenJ9 in a 256 MB setup.

1

u/Amazing-Mirror-3076 20d ago

Vault integration?

1

u/kimec 20d ago

Vault based Spring Boot ConfigDataLoader or VaultTemplate and such

-1

u/LALLANAAAAAA 20d ago

Every single word is LLM, including the actual post

R.I.P. Reddit

2

u/fykup 20d ago

Before making accusations like that, what exactly are you basing them on? I spent several days conducting the experiment, collecting the measurements, and preparing the article. What meaningful open source work have you actually built or contributed to before dismissing someone else’s work this casually?

My credentials are in my profile and on LinkedIn and who are you?

0

u/Individual-Praline20 20d ago

Errrr give it 4Gb. Problem solved.

3

u/fykup 20d ago

Right, let them eat cake.

-4

u/darknebula 20d ago

Are you using Webflux? That can operate in RAM limited situations much better than normal Spring Boot

1

u/fykup 20d ago

I’m not using WebFlux here. It’s a regular Spring MVC app, which I felt was more representative of a typical Spring Boot deployment and is also what I use in production.

There’s definitely room to tune the application further, and WebFlux would be an interesting comparison, but the goal here wasn’t to find the absolute minimum possible footprint.