Skip to Content

LESSONS LEARNED FROM MAINTAINING JAVA APPLICATIONS

February 4, 2026
Dennis Geurts

Maintenance is engineering with empathy for operations. It’s not only about keeping the code healthy; it’s about safeguarding the business value the code represents. Across diverse client landscapes, the teams that thrive are the ones that make non-functional requirements visible, keep deployments boring, and treat stakeholder communication as a first-class duty. These are the lessons that consistently pay off, especially when the stakes are high and the lights must stay green.

The “only truth” of source code, and the truths it cannot tell

Source code is the executable truth: it tells you what the system does, every time, without ambiguity. Yet maintenance lives in the gaps that code alone cannot bridge intent, operating context, and philosophy. That is why I practice Just Enough and Just-in-Time documentation.

Write down the reasons behind decisions, the contracts at system boundaries, the operational rituals that keep the service healthy, and the constraints that shape how change can happen safely. Keep these artifacts close to the code and treat them like code: version them, review them, and write them for your future teammates (including future you). Let well-chosen names and well-written tests describe the “what.” Use lean documents to capture the “why,” “how,” and “what next.”

Non-functional requirements decide your fate

If functionality is what earns trust, non-functionals are what preserve it. Of the many ISO 25010 quality attributes, I want to focus on the following three: security, performance, and maintainability.

Security that’s woven in, not bolted on

Security is not a parallel track; it’s a property of good design, careful code, and disciplined operations. Put secrets where they belong: in a vault with workload identities and rotation policies, not in source code or configuration files. Make logs structured and tamper-evident so they can answer who did what, when, where, and with what outcome. Practice supply chain hygiene like you would incident response: keep an SBOM per release, scan dependencies, and patch on a cadence you can defend.

Performance you can define, measure, and own

“Fast” is meaningless without context. Performance only becomes a business property when it is tied to user journeys and expressed as service level objectives that everyone can see. Agree on your key indicators: latency, throughput, and error rate. Define target behavior at realistic load. Design a workload model that mirrors reality: peaks and bursts, concurrency and payload sizes. Test in pre-production with scripts that simulate complete user journeys, including think time and cache warm-up, not just isolated endpoints. Bring observability forward by default, use metrics, logs, and distributed traces. Use them to tune connection and thread pools, and to discover where the bottlenecks truly live.

Maintainability that optimizes for change

Most maintenance pain is self-inflicted: ambiguity, tight coupling, and accidental complexity. The antidote is to design so that change has a home. Favor small functions, clear names, and explicit dependencies. Use architecture patterns that locate change, domain-driven design that names boundaries, and ports/adapters that isolate the infrastructure from the domain. Write tests that explain intent: unit for logic (FIRSTU-principle), integration for behavior, and provider/consumer contract tests where services meet.

If you can’t deploy, you can’t maintain

Deliverability is the backbone of maintainability. Without a dependable path from change to production, everything else is theory. Treat delivery as a first-class capability: make it repeatable, observable, and reversible.

Build CI/CD pipelines that compile, test, scan, and gate releases with confidence checks. Practice dry runs everywhere. Most importantly, test your rollback. Practice restores, and verify backups in lower environments. A rollback plan that lives only in a document is not a plan; it’s wishful thinking.

Stakeholders you can name, reach, and inform

Maintenance is a service, and services exist to serve. If you do not know who relies on your application and how, your risk is unbounded. Map your landscape. Identify the users and dependent systems that consume your APIs, events, and reports. Know the business owner who is accountable for outcomes, budgets, and priorities, and keep them close to the SLOs that govern the user journeys they care about.

Operational clarity turns chaos into confidence. Agree on incident communication who to notify, how, and when and make those pathways part of your practice, not just your policy. And complete an Operational Readiness Review before go-live or major changes to prove that the system is not just built, but operable.

“Source code is the only truth” is a powerful principle but maintenance thrives when that truth is wrapped in intent, measurability, and operational clarity. The teams that excel are the ones that make non-functional requirements visible, deployment predictably uneventful, and stakeholder communication habitual. Optimize for change, measure what matters, and practice rollback as diligently as you practice release. Then the lights don’t just stay green; they stay trustworthy.

About the author

Lead Software Architect | Netherlands
Dennis is a passionate software architect with over 15 years of experience in software development. His working area is mainly java back-end applications and application modernization projects.

Leave a Reply

Your email address will not be published. Required fields are marked *

Slide to submit