The reality I keep running into: software that "just works for years" requires dependency hygiene at the ecosystem level, not just the application level. You can write Common Lisp or C or even most of Go that way and your code will still run in 20 years. The moment you depend on a modern frontend framework or even a modern backend one, you've committed to following its release cadence — which is often "we deprecate things twice a year."
Framework authors have their own incentives (relevance, employment, hiring funnel) and aren't optimizing for your project's longevity. The only way to write 20-year code today is either (a) work in an ecosystem that genuinely values stability (Lisp, C, parts of Erlang/OTP, Postgres) or (b) accept the tax of a modern stack and budget for it explicitly.
Most teams do neither, which is when projects rot fastest.
The original Java standard class library is very capable, and you can do a lot using just 20+ years old classes. People responsible for Java platform design were thinking from perspective that software can be "done" and keep working for decades if not centuries.
But modern Java developers don't want to write this way, it's not "modern".
I guess incentives aka "job security" is the main force here. Would you rather say that a piece of software is "done" and can keep working for decades with only periodic maintenance required, or would you rather "we need to migrate from Quux to Baar as Quux is deprecated and unmaintained now, and Bazz might not be optimal from performance standpoint so we need a replacement. so yeah we'll be busy this year"?
And conference people are so helpful with "Baar is the only valid way to make Java now. If you think about rawdogging JCL you're a goblin"
> even most of Go that way and your code will still run in 20 years
While reading this, I was literally working on patching my open source go app [1] because this is what came out of the stdlib in the last few months: CVE-2025-30204, CVE-2026-33487, CVE-2026-25679, CVE-2026-27137, CVE-2026-32280, CVE-2026-32281, CVE-2026-32283, CVE-2026-33810, CVE-2026-33811, CVE-2026-33814, CVE-2026-39820, CVE-2026-39836, CVE-2026-42499
You can, and sometimes that's the right answer. Where it gets hard: security CVEs that need patching but the fix is only in the new major, transitive deps that bump and bring incompatibilities, hiring a contractor who doesn't know your locked version. None of those are insurmountable, but they're real tax.
Until you run out of hardware that can run an OS old enough that the old version of the framework works, sure. Even then, you may find it hard to add new dependencies without getting current on the framework, since most ecosystems require you to pick a winning version in the case of a "diamond" transitive dependency.
Well if you're adding new dependencies you're already doing new maintenance work and this is a while different ballgame than just to keep what you have working.
Yes and no. In practice sooner or later you need to update something - you need it to work with a new OS/database/TLS algorithm - and then often there's a domino effect.
the problem is that you have to keep all the artifacts around so you can keep building with the old framework. especially in the npm world that is incredibly annoying. my solution for javascript at least is to avoid build tools alltogether, and build the site in such a way that it runs without a build step only using frameworks that support that. since the code runs in the browser there are no security concerns because you can't trust that code anyways.
Which brings us to the next layer of modern dependency management insanity:
The fact that basically none of these multi-million dollar companies are vendoring their entire dependency tree.
At most companies, even ones worth millions of dollars, it would be impossible for them to rebuild their software if someone ripped a package off of npm’s registry or whatever.
Different languages definitely have differently expectations and cultures around this stuff. If an npm package hasn’t been updated in 2 years, I’m suspicious. I’m gonna check the downloads, check GitHub issues and stuff to see kind of problems people are having, Google around to see if people are using something else.
If a clojars package hasn’t been updated in 6 years, I don’t even think about it!!
npm package can contain binary code... it's an absolute disaster of an ecosystem, driven by "it works on my computer" and "I can build an app in 10 minutes!"
This again! Software can be both _mature_ and _useful_. If you trip across a piece of software that's both of mature and useful, your first action should be clone it's git repo into your own storage and save project state. Then you should work against your repo posting pull requests for the greater community. But if no one consumes the pull requests, move on.
This. The core problem is that people assume that all software is necessarily unreliable.
The fact is because they themselves are not capable of producing perfectly reliable software, they assume that everyone else is the same. With this narrow-minded worldview, you would expect software to require constant updates as the maintainer is essentially playing a never-ending game of whac-a-mole.
Not all technologies change. Often, low-level engine APIs are very stable and essentially never change... So why should the software built on top change?
According to OP, the kind of reliable software that we need in the AI slop era would fall in the category of 'dead project'. So they are doomed to create AI slop on top of other AI slop. Good luck to them.
Anything connected to the internet or integrated with 3rd party systems will have to be maintained regularly forever. Anything that is self contained will last forever.
Main reason I avoid buying anything that requires an app. Because one day that app won't be maintained anymore and it just wont work, bricking the hardware in the process.
Well, kind of, but I'd think over 30 years people would notice this problem and figure out how to factor out "internet" part from business logic part. (And you might want formal verification / memory safety / etc, so you can accept arbitrary inputs.)
But on contrary, business incentive is to make "everything app" which needs to be continuously patched.
The need for security update is largely due to poor development practices where safe and unsafe code is mixed together, lots of dependencies with unclear provenance and quality, etc.
We had a recipe for a much stabler stack decades ago: separate runtime (might need to be patched regularly) from a high-level business logic (never needs to be patched if done properly).
E.g. old way of developing web front-end was like that: you code directly in JS. It never needs to be patched, only browser needs to be patched.
Same thing with Excel/VBA, etc.
But new devs don't know any of that, they just want to use latest "framework" which pre-installs whole bunch of vulns. And if there's a patch you need to rebuild. Constant churn just to satisfy the trend
Or in the past code just sat unpatched via obscurity because fewer people were looking. After all there are plenty of exploits from injection to CSS that we have fixed or migrated away from for code from the far past
The idea that everything is so broken that it needs to be patched is just wrong.
A C program which just manipulates strings and other data structures can have a remote code execution vulnerability because C is a shitty language with no memory safety, data and control flow can be mixed, etc.
But that's just not true for high level code, say, in Python. If you don't use some low-level hacks, Python code just cannot corrupt memory, by construction, and it cannot cause RCE. You can execute attacker's code only if you use a language function which might execute code, say, eval or unpickle. But there's only a handful of such constructions and Python developers could easily implement hardening which would forbid any such calls, guaranteeing that only code which was written by developer gets executed.
Yes, occasionally there might be a logic flaw in code which needs to fixed, but it's not same as weekly updates - framework version 1.2.3 uses package 4.5.6 which has a vuln. That's only recent lunacy.
I'm not saying that e.g. everything written in Python in safe - but that old platforms were almost ready for "works forever" software, and we don't have that anymore,.
Separating the runtime from the business logic doesn't really work because the business logic has the authority to do anything it has the authority to do. It's the https://xkcd.com/1200/ problem all over again.
I don't think so. Business logic just works with data it has access to. Backup, encryption, access control can be separate concerns. A good programming stack would make sure you don't have RCE.
Flawed business logic might corrupt data, but that's much less rare than security vulns, and might be solved by versioning data (e.g. copy-on-write, even Windows had Volume Shadow Copy service which can take a snapshot of all data).
The main problem is that there's no incentive for software vendors to separate parts: e.g. app which processes financial records might also send/receive data over internet. If user had a more explicit control over flow of data (e.g. imagine n8n style pipeline) many logic flaws like sending data to wrong place could be eliminated.
It's just that we are used to coarse-grained permissions and abstractions defined back in 1970s. E.g. an app gets access to entire network stack and then can do anything - send telemetry, spam, download code, etc. If we had more high-level comms layer on top of app it could be much more inspectable.
> Business logic just works with data it has access to. Backup, encryption, access control can be separate concerns. A good programming stack would make sure you don't have RCE.
The problem is that the business logic tells everything else what to do and has authority to do everything that the program can do. Exploiting that way is fiddlier (it's like doing ROP only more so), but ultimately once the software gets into an unintended/unexpected state it's game over.
> It's just that we are used to coarse-grained permissions and abstractions defined back in 1970s. E.g. an app gets access to entire network stack and then can do anything - send telemetry, spam, download code, etc. If we had more high-level comms layer on top of app it could be much more inspectable.
Many have tried. If you try to require fine-grained permissions and user in the loop people just say yes to everything. Ultimately the user thinks they want to do the thing they said to do, and asking them again won't change that.
In the past we had software stacks where once code is written it's just done, it will keep working years and even decades later.
E.g. https://sapaclisp.common-lisp.dev/ you can download code written in 1993 and just load it in latest SBCL.