Java is a statically typed, garbage-collected, object-oriented programming language that was born in 1995 with nothing wrong with it and spent the next thirty years having everything wrong done to it.
The language itself — the core language, the one James Gosling designed at Sun Microsystems — was a genuine contribution to the craft. Garbage collection that freed programmers from manual memory management. A type system that caught errors at compile time. Platform independence through the JVM. A standard library that was sensible, organized, and documented. “Write once, run anywhere” was not just a marketing slogan; it was, within the tolerances of 1990s computing, actually true.
Java was the first mainstream language that a university graduate could learn in a semester and use to build real software. It was accessible. It was typed. It compiled. It ran. It did not require you to manage pointers, debug segfaults, or understand the C preprocessor. For a brief and shining moment in 1996, Java was the language of the future.
Then the enterprise arrived.
“The language that lets you describe a system.”
— As opposed to Go, which lets you build one, The Databases We Didn’t Build
The Good Years (1995–1999)
For approximately four years, Java was a normal programming language. You wrote a class. The class had methods. The methods did things. You compiled it. You ran it. It worked.
The standard library was organised around the principle that common tasks should be easy and uncommon tasks should be possible. java.io for files. java.net for networking. java.util for collections. java.lang.String for the thing that every program needs and every language handles differently. It was not exciting. It was not clever. It was correct, which is better.
A “Hello World” in Java 1.0:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Three lines of ceremony, one line of work. A reasonable ratio. A ratio that would not survive the decade.
The Enterprise Infection (1999–2010)
In 1999, Sun Microsystems released Java 2 Enterprise Edition — J2EE — and the language contracted a disease from which it has never fully recovered.
J2EE was not a platform for building software. J2EE was a platform for describing software. The distinction is critical and was missed by an entire generation of architects who believed that if you could diagram it, you had built it.
The core insight of J2EE was that enterprise software required standardised abstractions for common concerns: transactions, persistence, messaging, remote method invocation. This insight was correct. The implementation was as follows:
To display a message to a user, you needed:
- A Servlet — the thing that handles the HTTP request
- A JSP — the thing that renders the HTML (which could not exist in the Servlet because separation of concerns)
- A Session Bean — the thing that holds the business logic (which could not exist in the Servlet because the Servlet is for HTTP)
- An Entity Bean — the thing that represents the data (which could not be a plain object because the container manages its lifecycle)
- A DAO — the thing that talks to the database (because the Entity Bean cannot talk to the database directly, despite being the database representation)
- A DTO — the thing that transfers the data between the DAO and the Session Bean (because the Entity Bean is too heavy to pass around, being managed by the container it was designed to live in)
- A Service Locator — the thing that finds the Session Bean (because you cannot simply create one; the container creates it, and the container does not tell you where it put it)
- Four XML deployment descriptors —
web.xml,ejb-jar.xml,application.xml, and a vendor-specific descriptor for the application server, which is either WebLogic, WebSphere, or JBoss, each of which interprets the standard differently
To display a message. To a user. “Hello, World.”
The equivalent in Go:
fmt.Fprintf(w, "Hello, World!")
One line. No container. No lifecycle. No deployment descriptor. No XML. No DTO. No DAO. No Session Bean. No Entity Bean. No Service Locator. No vendor-specific interpretation of a standard that was supposed to eliminate vendor specificity.
The Design Patterns Infection
The Design Patterns book arrived in 1994, one year before Java. The timing was catastrophic.
Java’s enterprise ecosystem adopted the Gang of Four’s twenty-three patterns not as vocabulary — which is what they were — but as a building code. Every interaction required a Factory. Every variation required a Strategy. Every notification required an Observer. Every object required a Builder. The patterns, which the Gang of Four had observed in well-designed software, became requirements for all software, regardless of whether the design problems they solved were present.
The result was code that implemented more patterns than features. A class that fetched a user from a database might involve:
UserRepository(the interface — because you might swap the database; you won’t)UserRepositoryImpl(the implementation — of the interface nobody will swap)UserRepositoryFactory(creates the implementation — becausenewis insufficiently abstract)UserService(calls the repository — because the controller cannot call the repository directly; this would be “tight coupling,” which is the enterprise term for “code that works”)UserServiceImpl(the implementation of the service — yes, another Impl)UserDTO(carries the data — because the Entity is too heavy, being decorated with seventeen JPA annotations)UserMapper(converts Entity to DTO — because the computer cannot copy six fields without a dedicated class)
Seven classes to fetch a user. In Go, this is a function:
func GetUser(db *sql.DB, id int) (User, error) {
// twelve lines
}
The Go developer writes twelve lines, compiles, deploys a binary. The Java developer writes seven classes, fourteen files (each Impl needs its interface), four unit tests per class (twenty-eight total), a Spring configuration to wire them together, and a meeting to discuss whether UserMapper should live in the service package or the mapper package. The meeting takes longer than the Go developer’s entire implementation.
“Where’s the AbstractStorageProviderFactory?”
“You’re looking at it.”
— The Squirrel and riclib, The Databases We Didn’t Build
Spring: The Cure That Became the Disease
Spring Framework arrived in 2003 to save Java from J2EE. Rod Johnson wrote a book called Expert One-on-One J2EE Design and Development whose thesis was: J2EE is too complex, and here is a simpler alternative.
The simpler alternative was dependency injection. Instead of Service Locators and JNDI lookups and XML deployment descriptors, you would annotate your classes and Spring would wire them together at startup. Magic. Clean. Simple.
Then Spring grew.
Spring MVC. Spring Data. Spring Security. Spring Batch. Spring Integration. Spring Cloud. Spring Boot. Spring Boot Actuator. Spring Cloud Config. Spring Cloud Gateway. Spring Cloud Netflix (ironic, given that Netflix’s architecture talks were the patient zero of the Microservices epidemic). Each module added abstractions on top of abstractions, configurations on top of configurations, annotations on top of annotations, until the framework that was supposed to simplify Java enterprise development became the most complex thing in Java enterprise development.
AbstractSingletonProxyFactoryBean is a real class in the Spring Framework. It is not a parody. It is an abstract base class for creating proxy factory beans that produce singletons. Every word in that name is a Design Patterns pattern. It is a five-pattern CamelCase tower, and it exists because Spring needed to abstract over the process of abstracting over the process of creating objects — which is what new does, in one keyword, in every other language on earth.
The Performance Question
Java is not slow. The JVM is, by any modern benchmark, a sophisticated and highly optimised runtime. The JIT compiler produces native code that competes with C++ on many workloads. The garbage collectors — G1, ZGC, Shenandoah — are engineering marvels.
Java applications are slow. Not because the language is slow, but because the enterprise ecosystem wraps every operation in so many layers of abstraction that the actual work — the database query, the HTTP response, the business logic — is buried under a geological stratum of proxy beans, aspect-oriented interceptors, reflection-based dependency injection, and annotation processing.
The JVM starts in milliseconds. A Spring Boot application starts in seconds — sometimes many seconds — because it must scan the classpath, process annotations, instantiate beans, resolve dependencies, create proxies, and configure twelve subsystems before it can serve a single request.
A Go binary starts and serves its first request in the time a Spring Boot application spends discovering which beans have circular dependencies.
THE BINARY WORKS
THE BINARY HAS ALWAYS WORKED
THE BINARY WILL OUTLIVE THE CONFERENCE
The Exodus
The migration away from Java began slowly — Scala, Kotlin, Clojure, all running on the JVM, all trying to fix Java without leaving the ecosystem. It accelerated when Go proved that you could build serious software with interfaces, functions, and if err != nil, and that the resulting binary would deploy with scp and run until you stopped it.
It became a stampede when Oracle started suing.
The Oracle litigation — covered in the Oracle article under “The One Good Deed” — did what decades of technical advocacy could not. It made Java legally uncertain. And legal uncertainty, in enterprise software, is the one force stronger than architectural inertia. A CTO will tolerate AbstractSingletonProxyFactoryBean. A CTO will not tolerate a licensing audit.
Companies that had spent twenty years building on Java discovered, upon migrating to Go or Rust or Python, that their applications were faster, smaller, simpler, and free of both proxy factory beans and legal exposure. The patterns did not follow. The abstractions did not follow. The seven classes to fetch a user became one function. The twenty-eight unit tests became four.
The Squirrel watched this exodus with complicated feelings. It had liked the patterns. It had liked the interfaces and the impls and the factories and the strategies. It had liked the architecture meetings about package structure. In Go, there were no architecture meetings about package structure. There was a main.go and a function that did the thing.
The Squirrel missed the ceremony. The Lizard did not.
What Remains
Java still runs the world. The banking systems, the insurance platforms, the government portals, the airline booking engines — much of it is Java, and much of it works, and much of it will outlive the companies that built it because nobody dares touch it.
This is Java’s final form: not a language for building new things, but a language for maintaining things that were built during the Enterprise Years and are now too interconnected, too decorated with annotations, and too frighteningly large to rewrite. The AbstractSingletonProxyFactoryBean is load-bearing. Remove it and something — nobody knows what — will break.
James Gosling designed a language for building software. The enterprise used it to build cathedrals. The cathedrals still stand. Nobody can find the door.
And somewhere, a Go developer copies a binary to a server, types systemctl restart, and serves the same twelve hundred users in fifty milliseconds.
