Results at a Glance
| Metric | Result |
| Security issues resolved | 9 (critical + high + medium) |
| Tests delivered from zero | 266 across 14 test classes |
| God class eliminated | 1,777 LOC decomposed into 7 services |
| Static analysis | 0 SpotBugs findings, 0 Checkstyle violations |
Engagement Snapshot
| Industry | Non-profit / Professional Membership Association |
| Location | United States |
| System type | Identity management — Active Directory + OpenLDAP member provisioning |
| Legacy stack | Java 8 (EOL March 2025), 24 source files, 6,105 LOC |
| Target stack | Java 21 LTS, 28 source files, GitHub Actions CI/CD |
| Scope | 17 CLI programs, 1 god class (CC=285), zero tests, zero CI/CD |
| Delivery model | Structured 7-phase execution — assessment → security → decomposition → testing → quality gates → CI/CD → validation |
About the Client
The client is a prominent US-based professional membership academy managing member accounts for thousands of fellows across multiple academy divisions. Their identity management infrastructure (a Java 8 CLI batch suite called NAS-LDAP-Utilities) synchronized member records from their Impexium membership database into Microsoft Active Directory and OpenLDAP, handling account creation, lifecycle changes, group assignments, and credential email dispatch.
The system had operated reliably for years, but had accumulated critical security debt that could not be deferred: Java 8 reached end-of-life in March 2025, the codebase had no tests and no CI/CD, and multiple high-severity security vulnerabilities were present in production.
Challenge
The modernization required resolving four compounding categories of risk simultaneously, on a live identity system with no test coverage to validate changes against.
End-of-Life Runtime with Active Security Vulnerabilities
Java 8 reached end-of-life in March 2025, meaning no further security patches. The codebase carried nine unresolved security issues on top of the EOL risk: SSL certificate validation was completely disabled across all 9 Active Directory connections via TrustAllTrustManager, making MITM attacks technically possible. All 7 OpenLDAP connections ran on plain unencrypted LDAP port 389, transmitting bind credentials and passwords in cleartext. Two programs contained hardcoded plaintext passwords (“start123”, “startnewpass”). Initial member passwords were derived from a deterministic algorithm using name and member ID, predictable if the algorithm was known.
1,777-Line God Class with Cyclomatic Complexity of 285
All 44 LDAP, AD, SMTP, and utility operations were concentrated in a single static Operations.java class of 1,777 lines of code, with a cyclomatic complexity score of 285. Every one of the 17 CLI programs called into it. Any change to Operations carried the risk of unintentionally breaking unrelated functionality across the entire suite. This made safe refactoring nearly impossible without a test suite, and building a test suite nearly impossible without refactoring the god class first.
Zero Tests, Zero CI/CD on a Production Identity System
The codebase had no automated tests whatsoever – not unit tests, not integration tests, not even smoke tests. There was no CI/CD pipeline. Changes were validated manually before deployment. With 24 source files and 6,105 lines of code managing live member account provisioning, deceased account handling, group synchronization, and credential email dispatch, there was no safety net for the modernization work.
Non-Functional Stubs, Dead Code, and Copy-Paste Artifacts in Production
ldapDeleteGroup, a program that operators could invoke to delete LDAP groups, was a non-functional stub: the deletion logic was commented out, so invoking it produced no error and no deletion. adDeleteDeceased and adEnableAccounts both performed operations without pre-validating account state.
Three CLI programs contained a copy-paste artifact (new ldapImport() at startup with no functional effect). Dead code blocks, including an unused Derby database connection layer, an unreferenced trust manager, and dead password encoding methods, added maintenance surface with no operational value.
How Legacyleap Executed the Modernization
The engagement followed a structured 7-phase execution designed to address security first, then structural debt, then quality gates, with each phase building the foundation for the next.
Phase 1: Assessment and dependency mapping
Legacyleap’s AI-powered assessment tool analyzed all 24 source files, producing a full Application Overview documenting architecture, workflows, data model, security risks, and technical debt. Every CLI program, every call path into Operations.java, and every dead code block was mapped before a single line was changed.
Phase 2: Security remediation
Every critical and high-severity security issue was addressed: NaiveTrustManager rewritten with TrustManagerFactory delegation and custom KeyStore support; TrustAllTrustManager eliminated from all 9 AD CLIs; hardcoded passwords externalized to environment variables via ConfigService; ConnectionFactory created with StartTLS for all OpenLDAP connections; SMTP upgraded to SMTP AUTH + STARTTLS; CLI credential exposure mitigated by env var/properties resolution as primary path.
Phase 3: God class decomposition
Operations.java (1,777 LOC, CC=285) was decomposed into 7 focused service classes: LdapUserService (user lifecycle), LdapGroupService (group management), NotificationService (email), ConfigService (configuration), PasswordService (SecureRandom generation), ConnectionFactory (LDAP connection), and AuditService (structured execution summaries as Java 21 records). All 17 CLI callers were updated. Operations.java was deleted.
Phase 4: Dead code removal and CLI modernization
NASConstants, Derby DB methods, dead password chain, copy-paste artifacts, and wrong constructors were removed. All 17 CLI programs received try-with-resources, parameterized logging, and StandardCharsets.UTF_8 for all file I/O, and pre-validation added to adDeleteDeceased and adEnableAccounts. Java version bumped to 21 LTS with var type inference throughout.
Phase 5: Test suite built from scratch
266 tests across 14 test classes, covering integration tests via InMemoryDirectoryServer for LDAP operations, unit tests for all service classes, DTOs, CSV I/O, and configuration resolution chains. Tests cover duplicate detection, username collision, accountExpires logic, the validUser bug fix, SecureRandom password generation, SMTP AUTH contract, and the full ConfigService env var → properties → CLI arg precedence chain.
Phase 6: Quality gates established
SpotBugs (effort=MAX, reportLevel=HIGH) reduced from 19 findings to 0. Checkstyle reduced from 15 violations to 0. All 8 runtime dependencies upgraded, including a 13-version Java jump, UnboundID LDAP SDK major upgrade, and javax.mail → angus-mail 2.0.5 namespace migration. JaCoCo added for test coverage reporting.
Phase 7: CI/CD pipeline and structural validation
GitHub Actions workflow established: triggers on push to main/develop and PRs to main, runs clean build → spotbugsMain → checkstyleMain, uploads test/JaCoCo/SpotBugs/Checkstyle reports as artifacts. Structural validation confirmed: 24 → 41 entity nodes, 150 → 400 behaviour nodes, USES edges into Operations.java reduced from 1,965 to 0.
Security Issues Resolved
| Issue | Severity | What Was Found | What Was Done |
| SSL bypass (9 AD CLIs) | Critical | TrustAllTrustManager disabled all certificate validation | TrustManagerFactory delegation implemented; custom KeyStore via env var |
| Hardcoded “start123” | Critical | Plaintext password literal in setPassCn.java | Externalized via ConfigService with fail-safe exit if unset |
| Hardcoded “startnewpass” | Critical | Applied to every new OpenLDAP user in ldapImport | Same externalization pattern — refuses to proceed without configured password |
| Deterministic initial passwords | Critical | UppercaseFirstInitial + lowercaseLastInitial + memberID algorithm | PasswordService.generateSecurePassword() using SecureRandom |
| Plaintext LDAP (7 CLIs) | High | Port 389, cleartext bind credentials and passwords | ConnectionFactory with StartTLSExtendedRequest on all connections |
| CLI credential exposure | High | Bind DN/password visible in process listings and shell history | ConfigService resolves from env var / properties first; CLI args as fallback only |
| Unauthenticated SMTP | Medium | Port 25, no authentication, no TLS | NotificationService with SMTP AUTH and STARTTLS |
| validUser bug | Medium | setValidUser(true) set before null check on search entry | Moved inside if (entry != null) block |
| Default file encoding | Low | FileReader without charset — platform-dependent | StandardCharsets.UTF_8 applied across all 17 CLIs |
Results
| Metric | Before | After |
| Java version | 8 (EOL March 2025) | 21 LTS (supported to 2028+) |
| Automated tests | 0 | 266 across 14 test classes |
| Critical security issues | 4 | 0 |
| High security issues | 2 | 0 |
| God class | 1,777 LOC / CC=285 / 44 static methods | Deleted — decomposed into 7 services |
| SpotBugs findings | 19 | 0 |
| Checkstyle violations | 15 | 0 |
| CI/CD pipeline | None | GitHub Actions (push + PR triggers) |
| Dead code / stubs | 5 categories present | All removed |
| Source LOC | 6,105 | 5,761 (−344 dead code removed) |


