Functional vs Object-Oriented Approach in Java: What Should a Modern Developer Choose?
\n\nThe world of Java is a battlefield of two giants. On one side is the classic object-oriented programming (OOP), on which a whole generation of developers was raised. On the other is the increasingly popular functional approach, which burst into the ecosystem with the release of Java 8, and debates still rage: "Do we even need it?".
\n\nIf you write in Java, you face this choice daily. Should you create an entire class with methods for a simple filtering task? Or is it better to throw together a chain of lambdas and forget about it? In this article, we won't take sides. We will break down both approaches with real examples, compare them by key metrics, and help you understand when to use which tool from your arsenal.
\n\nA Brief Overview: How We Got Here
\n\nJava was originally conceived as a purely object-oriented language. Everything is an object. Even a string is an object of the String class. Classes, inheritance, polymorphism — this was the religion. But time passed, and developers realized: OOP is great for modeling the real world (cars, employees, orders), but it handles data stream processing terribly.
Then lambda expressions and the Stream API entered the stage. Java didn't become a functional language (like Haskell or Scala), but it took the best from that paradigm. Now we have a choice: write a for loop inside a class method or use filter().map().collect().
Object-Oriented Approach: The Classic
\n\nOOP in Java is built on four pillars: encapsulation, inheritance, polymorphism, and abstraction. When you work with business logic that involves entities (User, Order, Product), OOP is the natural choice.
\n\nHere's an example of a classic task: find the sum of all orders for a customer made in 2024.
\n\nObject-Oriented Solution:
\n\nimport java.util.List;\n\npublic class OrderService {\n\n public double calculateTotal2024(List<Order> orders) {\n double total = 0.0;\n for (Order order : orders) {\n if (order.getYear() == 2024) {\n total += order.getAmount();\n }\n }\n return total;\n }\n}\n\nclass Order {\n private int year;\n private double amount;\n\n public Order(int year, double amount) {\n this.year = year;\n this.amount = amount;\n }\n\n public int getYear() { return year; }\n public double getAmount() { return amount; }\n}\n\nEverything is transparent, readable, and easy to debug. Any junior developer will understand what's happening here. But there's a nuance: the code is imperative. We are telling how to do it (create a variable, iterate through the list, check the condition, sum).
\n\nFunctional Approach: Writing Declaratively
\n\nFunctional programming in Java is about what we want to get, not how. We use higher-order functions, immutability, and the absence of side effects.
\n\nThe same task, but in a functional style:
\n\nFunctional Solution:
\n\nimport java.util.List;\n\npublic class OrderServiceFunctional {\n\n public double calculateTotal2024(List<Order> orders) {\n return orders.stream()\n .filter(order -> order.getYear() == 2024)\n .mapToDouble(Order::getAmount)\n .sum();\n }\n}\n\nJust three lines. No mutable variables, no loops. We simply say: "Take the stream, filter by year, extract the amounts, sum them." This is a declarative style.
\n\nComparison: Where Does Each Win?
\n\nLet's break down in which scenarios each approach performs better.
\n\n1. Readability and Conciseness
\nWinner: Functional appro