Java's OOP: A Historical Misunderstanding?
When Alan Kay created Smalltalk in the 70s, his vision of "message passing" between autonomous objects departed from what the term "Object-Oriented Programming" would later come to mean. For him, the essence lay in "message passing" [1]: a decentralized system of autonomous cells communicating with each other, more inspired by biological systems than a class hierarchy.
Java, arriving in the 90s, took a radically different direction by focusing on mutable state manipulation within massive class structures. This choice, while performant for the time, established a fundamental misunderstanding: we thought we were doing object-oriented programming, while we were building complex and fragile object graphs, where data and behavior were often too intimately linked to remain manageable.
This gradual success wasn't so much due to the "purity" of this model as to the technical promise of the JVM: "Write
Once, Run Anywhere". This success came with a certain architectural rigidity: the tendency to encapsulate
all logic in classes, even for purely algorithmic treatments, even though static methods offered an alternative. While patterns like Service or Factory have clearly
proven their utility for structuring large systems, their systematic use sometimes led to "over-engineering" where
business logic disappears under layers of abstractions. We too often sought to solve state management difficulties
through deep or fragile inheritance hierarchies, or through a multiplication of complex design patterns where a simple
function would have sufficed.
The Other Way: Lambda Calculus and Functional Programming (FP)
In parallel with the rise of imperative programming, another school of thought developed on much older mathematical foundations: Alonzo Church's Lambda Calculus. Where OOP focuses on state and its mutation, functional programming (FP) treats software as an evaluation of pure functions. Based on immutability and referential transparency, this approach offers appreciable reasoning comfort: a function called with the same arguments will always produce the same result, without unexpected side effects.
This philosophy radically transforms the development experience. It expresses itself well through the REPL (Read-Eval-Print Loop), a powerful tool inherited from Lisp and Scheme traditions, later adopted by languages like Clojure and Python. Unlike OOP where testing a method often requires instantiating a class and simulating a whole internal state (the "tyrannie of state"), FP allows evaluating any fragment of logic instantly. This fluidity is naturally found in testing: testing a pure function is simpler (input -> output), reducing the need for complex mock frameworks and eliminating certain forms of boilerplate.
Yet, despite these advantages, FP was long perceived as an unaffordable luxury. The reason is primarily physical: our computers rely on the Von Neumann architecture, where memory mutation is the fundamental operation. Running pure functional code amounts to emulating a paradigm on a machine designed for its opposite. The obstacles were real: memory consumption due to copies, recursion-related "StackOverflow" risks, and a psychological entry barrier to abstract mathematical concepts. While languages like Erlang (used in telecommunications) or Scala showed the viability of FP, its adoption remains gradual in the mainstream, often through hybrid approaches rather than pure FP.
Hybridization: The Best of Both Worlds
Faced with the tension between dominant OOP and theoretical FP, a third way emerged: hybridization. Architects began to adopt the "Functional Core, Imperative Shell" pattern [3]: isolating business logic in a pure functional core and managing interactions with the external world in a thin imperative layer. This approach found concrete validation with React, showing that a "flexible FP" could simplify the construction of complex interfaces.
Under the impetus of Brian Goetz, Java began this pragmatic mutation. Goetz, while admiring Clojure's clarity [2], refused to turn Java into a purely functional language. His approach was that of incremental evolution: identifying the most useful functional patterns — those that actually reduce errors — and integrating them idiomatically. This fundamental work, begun with Lambdas in Java 8, laid the groundwork for a more radical transformation: the transition to data-oriented programming.
Data-Oriented Programming (DOP): The New Face of Java
This is where Data-Oriented Programming (DOP) comes in [4], the practical fruition of this evolution. DOP prioritizes explicit and immutable data structures, rather than hiding them in object hierarchies.
Java 21 and following versions (including the current version 25) instantiate this vision through three pillars:
- Records: Immutable and transparent data carriers, eliminating the boilerplate of typical getters/setters.
- Sealed Types: They allow modeling closed hierarchies, precisely defining the possible alternatives of a data point.
- Pattern Matching: A powerful mechanism (still in maturation) for deconstructing and handling these structures with precision. Combined with Sealed Types, the compiler can detect unhandled cases.
By combining these elements, Java allows modeling application domains less as actor hierarchies, more as explicit data structures. Rather than encapsulating data and behavior in the same class, we separate the structure of data from the logic that transforms it. This is a more direct approach and easier to reason about, combined with Java's type safety.
Perspectives: Java as a Mainstream Language Influencer
By becoming multi-paradigm, Java progressively redefines its positioning. Java, long criticized for its heaviness, now offers a balanced approach: the rigor of OOP for structuring systems, FP tools for clarifying logic, and DOP for representing data explicitly. Java maintains its dominant role in enterprise software while gaining recognition for these design choices. More than a quarter-century after its creation, Java continues to evolve, incorporating lessons from more recent paradigms while remaining pragmatic.
References
[1] Alan Kay, on the term "Object-Oriented": "I'm sorry that I long ago coined the term 'objects' for this topic because it gets many people to focus on the lesser idea. The big idea is 'messaging'." (Source)
[2] Brian Goetz, interview with Nicolai Parlog (2021): "Clojure is the most interesting language on the JVM because it is materially different from Java." (Video)
[3] Gary Bernhardt, "Boundaries" (RubyConf 2012): Talk introducing the Functional Core, Imperative Shell pattern. (Video)
[4] Brian Goetz, "Data-Oriented Programming in Java" (InfoQ, 2022): Foundational article defining DOP principles applied to Java. (Article)