I Built a "Write Once, Run Anywhere" Hybrid Cloud SDK — 3 Months Later, Here's What Nobody Tells You
Honestly. I fell for the hype.
"Write once, run anywhere" — doesn't that sound like the holy grail of backend development? One codebase, deploy to any cloud, any runtime, no messy configuration, no vendor lock-in. I built Capa-Java to solve exactly this problem for hybrid cloud environments. Three months later, I have some very mixed feelings about it.
Let me tell you the real story. No marketing fluff, just what actually happened when I tried to make "write once run anywhere" work in 2026.
What Problem Was I Actually Solving?
My team was dealing with this exact pain point: we had to deploy the same application to multiple environments — some on AWS, some on-prem, some to Kubernetes clusters managed by different business units. Each environment had different service discovery mechanisms, different configuration systems, different security requirements.
Every deployment required copying code, changing configs, debugging environment-specific issues. It was exhausting. So I thought: what if we could build a thin abstraction layer that handles all this runtime stuff automatically? You write your business logic once, drop it into any runtime, and it just works.
That's exactly what Capa-Java does. Let me show you how it actually works with code.
How It Works (Code Example)
Here's a minimal example of a Capa-Java application. It's actually pretty clean:
// Your business logic — pure Java, no framework dependencies
public class UserService {
public User getUser(String id) {
// ... your business logic
return new User(id, "Kevin");
}
}
// Capa-Java bootstrap — zero configuration
public class Main {
public static void main(String[] args) {
CapaBootstrap.create()
.addApplication(new UserApplication())
.run(args);
}
}
// Define your application capabilities
public class UserApplication implements CapaApplication {
@Override
public void registerServices(CapaRegistry registry) {
// Register your services — they'll be available anywhere
registry.register(UserService.class, new UserService());
}
@Override
public void registerEndpoints(CapaEndpointRegistry registry) {
// Expose REST endpoints automatically
registry.get("/api/users/{id}", (ctx) -> {
UserService service = ctx.getRegistry().get(UserService.class);
return Response.ok(service.getUser(ctx.pathParam("id")));
});
}
}
That's it. That's the whole application. Now you can run this:
-
As a standalone Spring Boot app:
mvn spring-boot:run - Inside an existing Spring Boot app: just add the dependency and it auto-configures
- As a bare JVM application: Capa has its own lightweight HTTP server built-in
- In a Kubernetes cluster: automatic service discovery registration
- In a traditional application server: deploy as a WAR and it just works
The same exact code. No changes. That's the promise delivered.
So what's the problem? Because if it works, why am I writing this with a title like this?
The Brutal Truth #1: Everyone Says They Want Hybrid Cloud — Nobody Actually Does
I learned this the hard way. You know what most companies actually want when they say "hybrid cloud"?
They want two completely separate deployments with completely separate teams. One team manages AWS, one manages on-prem. They don't want a unified layer — they want their own silos. Because organizational boundaries trump technical boundaries every single time.
I built this beautiful technical abstraction that solves a real technical problem... and discovered that the problem wasn't actually technical. It was organizational. The organizations that need hybrid cloud the most are the least likely to allow a unified abstraction layer.
Pros:
- ✅ Actually delivers on the "write once run anywhere" promise
- ✅ Minimal overhead — about 5-10ms extra per request
- ✅ Works with existing codebases incrementally — you don't have to rewrite everything
Cons:
- ❌ Most organizations don't actually want unified hybrid cloud (politics > code)
- ❌ You still need to understand each target environment — abstraction doesn't mean you can ignore it
- ❌ Debugging across multiple runtimes gets weird fast
The Brutal Truth #2: Abstraction Has a Cost — And It's Not Just Performance
People always talk about performance cost when you add abstraction. And yeah, there is some — but honestly, it's negligible for 99% of applications. Capa-Java adds about 5-10ms per request. That's nothing compared to your database query taking 100ms.
The real cost is debugging complexity.
When something goes wrong, where do you look? Is it your code? Is it Capa's runtime detection? Is it the environment-specific adapter doing something weird? I've spent hours debugging an issue that turned out to be... a combination of three different layers each doing exactly what they were supposed to do, but together they broke.
Here's an example from last month: we deployed to a new Kubernetes cluster. Everything looked fine — service discovery registered correctly, health checks passed. But requests were timing out after 30 seconds.
Turned out:
- Capa automatically detected Kubernetes and activated the cluster-friendly configuration
- The cluster had a different default timeout for health checks than we expected
- Capa's graceful shutdown handler was waiting longer than the cluster allowed
- The cluster killed the pod before it fully started
- We got timeout errors that didn't point to any of this
It took me 3 hours to figure it out. If we'd deployed a plain vanilla Kubernetes manifests without the abstraction, we'd have fixed it in 10 minutes.
Pros:
- ✅ Incremental adoption — you can add it to existing projects
- ✅ Sensible defaults that work for most cases
- ✅ Good abstraction that actually hides the messy parts when it works
Cons:
- ❌ Extra layer of indirection when debugging
- ❌ Some edge cases with custom runtime configurations
- ❌ You still need to know what's happening under the hood — abstraction isn't magic
The Brutal Truth #3: Configuration Is Still Hard — Just Different Hard
So here's the thing about configuration in a multi-runtime world. Capa-Java solves the problem of different configuration locations — it pulls configuration from environment variables, system properties, cloud parameter stores, whatever — automatically based on where you're running.
That's great! But... now you have to think about configuration across all those environments at once. Before, you just had different config files for different environments. Now you need a single config that works everywhere. Which sounds easier — but it's actually harder.
Because different environments have different security requirements. In dev, you can hardcode credentials. In prod, you must pull them from a secure store. Capa handles that — but now you have to structure your config to work in both modes, which means learning how Capa does it, which adds another thing to learn.
Let me show you what configuration looks like:
# application-capa.yaml — works everywhere
capa:
runtime:
# Capa auto-detects, but you can override if needed
auto-detect: true
http:
# Port will be auto-detected from environment if available
port: 8080
# Timeouts are different in different environments
connection-timeout: ${CONNECTION_TIMEOUT:30s}
service-discovery:
# Enabled automatically when running in Kubernetes
enabled: ${SERVICE_DISCOVERY_ENABLED:auto}
namespace: ${NAMESPACE:default}
This is actually pretty clean. But when you're troubleshooting why your config isn't loading in a specific environment, you have to understand Capa's config loading order, which environments override what, how the auto-detection interacts with your explicit settings. It's not rocket surgery — but it's another thing to learn.
Pros:
- ✅ Auto-detection does the right thing 90% of the time
- ✅ Unified configuration format across all environments
- ✅ Proper 12-factor compliant config handling
Cons:
- ❌ Learning curve for the config system
- ❌ Overlapping config sources can cause unexpected behavior
- ❌ "It works on my machine" still happens, just for different reasons
Real-World Performance Numbers
I ran some benchmarks comparing:
- Plain Spring Boot (no Capa)
- Spring Boot with Capa
- Capa standalone (built-in HTTP server)
All running on my development machine (Ryzen 5800X, Java 21):
| Scenario | Avg Response Time | Throughput (req/sec) |
|---|---|---|
| Plain Spring Boot | 23 ms | 4280 |
| Spring Boot + Capa | 28 ms | 4010 |
| Capa Standalone | 18 ms | 5120 |
Interesting, right? The standalone mode is actually faster than plain Spring Boot because it's not carrying all of Spring Boot's overhead that you might not need. The integrated mode adds about 5ms — which is nothing for most apps.
So performance really isn't the issue. It's never been the issue. The issue is everything around performance.
Who Should Actually Use This?
After three months of real-world use, I can confidently say: Capa-Java works great if you actually fall into one of these categories:
✅ Good Fit
- You're a library author building components that need to run in multiple environments
- You have a small team that needs to deploy to different clouds and wants to minimize duplication
- You're building a product that customers deploy in their own infrastructure (you have no idea where they'll put it)
- You're prototyping and want to keep your options open
❌ Not a Good Fit
- You're 100% on a single cloud — you don't need this at all
- Your organization already has standardized deployment pipelines — adding another layer just creates friction
- Your team doesn't have bandwidth to learn a new abstraction
- You need ultra-ultra-max performance for every single request (though honestly, it's probably still fine)
Would I Build It Again?
That's the big question, isn't it? If I could go back three months, would I still build Capa-Java?
Honestly? Yes.
Even though most organizations don't actually need what it solves, the project itself was a great learning experience. I learned so much about how different runtime environments work, where the pain points actually are, how to build good abstractions that don't get in your way.
And for the right use case — like when you genuinely need to deploy the same code to multiple different environments — it really does deliver on the promise. My team actually uses it in production for one of our products that gets deployed to customer on-prem infrastructure, and it's working great there. We haven't had any major issues, and it's saved us countless hours of duplicated configuration and deployment code.
The mistake I made was thinking that every hybrid cloud organization needed this. The truth is — most of them don't. But some do. And for those that do, it's pretty great.
Check It Out Yourself
If you're still reading, you're probably curious. Go check it out on GitHub — it's open source, MIT license, everything:
GitHub: https://github.com/capa-cloud/capa-java
Star it if you think the idea is interesting — even if you don't need it today. It's been a fun side project, and I'm planning to keep working on it. There's already open issues for better documentation, more runtime adapters, and some debugging improvements based on what I've learned.
Your Turn
Have you ever built an abstraction layer that solved a real technical problem... that turned out not to be the problem anyone actually needed? Or have you tried to do the whole "write once run anywhere" thing and ended up with similar lessons?
Drop a comment below — I'd love to hear your stories. We learn more from failed experiments than successful ones, right?
What's the most interesting "technical solution searching for a problem" you've built? Let me know in the comments!
Top comments (0)