Java

Functional Programming Patterns

Functional programming concepts have transformed how we write clean Java code. This article covers pure functions, immutability, stream patterns, and how to apply functional thinking to make your code more readable, testable, and maintainable.

📋 At a Glance

AspectDetails
ConceptsPure Functions, Immutability, Higher-Order Functions
Java FeaturesStreams, Optional, Lambdas, Method References
BenefitsEasier testing, no side effects, thread safety
Trade-offsLearning curve, occasional performance overhead

🎯 What You'll Learn

  • Pure functions and why they matter
  • Immutability patterns for safer code
  • Stream patterns for clean data transformations
  • Higher-order functions for reusable logic
  • When to go functional vs imperative

Production Story: The Side Effect Bug

A payment reconciliation service had a bug that took weeks to find:

JAVA(17 lines)
Code
Loading syntax highlighter...
The bug: When called from multiple threads or called twice, runningTotal accumulated incorrectly.
The fix: Pure functions with no shared state:
JAVA(15 lines)
Code
Loading syntax highlighter...

Mental Model: Pure vs Impure Functions

TEXT(36 lines)
Code
Loading syntax highlighter...

🔬 Deep Dive

Pattern 1: Pure Functions

JAVA(25 lines)
Code
Loading syntax highlighter...
Benefits of pure functions:
  • Testable: No mocking required
  • Cacheable: Same input = same output (memoization)
  • Thread-safe: No shared mutable state
  • Composable: Can chain without side effects
JAVA(19 lines)
Code
Loading syntax highlighter...

Pattern 2: Immutability

JAVA(51 lines)
Code
Loading syntax highlighter...
Immutable collections:
JAVA(19 lines)
Code
Loading syntax highlighter...

Pattern 3: Stream Patterns

JAVA(40 lines)
Code
Loading syntax highlighter...
Stream vs Loop - when to use which:
JAVA(19 lines)
Code
Loading syntax highlighter...

Pattern 4: Higher-Order Functions

JAVA(70 lines)
Code
Loading syntax highlighter...

Pattern 5: Optional Patterns

JAVA(50 lines)
Code
Loading syntax highlighter...

Pattern 6: Functional Pipeline

JAVA(55 lines)
Code
Loading syntax highlighter...

Pattern 7: Collectors for Complex Aggregations

JAVA(53 lines)
Code
Loading syntax highlighter...

⚠️ Common Mistakes

Mistake 1: Mixing Side Effects in Streams

JAVA(18 lines)
Code
Loading syntax highlighter...

Mistake 2: Overusing Streams

JAVA(21 lines)
Code
Loading syntax highlighter...

🐛 Debug This: The Stream Side Effect

A developer reports: "My parallel stream works fine in tests but produces inconsistent results in production!"

JAVA(37 lines)
Code
Loading syntax highlighter...
Why are the results inconsistent, and how would you fix it?

✅ Solution:

Three major problems with parallel streams and shared mutable state:

  1. ArrayList is not thread-safe - concurrent add() calls can corrupt the list or lose elements
  2. BigDecimal accumulation is not atomic - totalRevenue = totalRevenue.add(...) is a read-modify-write operation that suffers from race conditions
  3. Side effects in forEach - parallel streams and side effects don't mix
Correct functional approach:
JAVA(53 lines)
Code
Loading syntax highlighter...
The lesson: Never use shared mutable state with parallel streams. Use immutable collectors and functional operations that return new values.

💻 Exercises

Exercise 1: Convert to Pure Function

⭐ Difficulty: Easy | ⏱️ Time: 10 minutes

Task: Refactor this impure function to be pure.
JAVA(12 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(25 lines)
Code
Loading syntax highlighter...

Exercise 2: Stream Refactoring

⭐⭐ Difficulty: Medium | ⏱️ Time: 15 minutes

Task: Refactor these imperative loops to functional streams.
JAVA(32 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(22 lines)
Code
Loading syntax highlighter...

Exercise 3: Higher-Order Function

⭐⭐ Difficulty: Medium | ⏱️ Time: 20 minutes

Task: Create a higher-order function for retry logic with exponential backoff.
JAVA(6 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(67 lines)
Code
Loading syntax highlighter...

Exercise 4: Immutable Builder

⭐⭐⭐ Difficulty: Medium-Hard | ⏱️ Time: 20 minutes

Task: Create an immutable query builder using functional patterns.
JAVA(10 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(78 lines)
Code
Loading syntax highlighter...

Exercise 5: Custom Collector

⭐⭐⭐⭐ Difficulty: Hard | ⏱️ Time: 25 minutes

Task: Create a custom collector that computes running statistics.
JAVA(14 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(73 lines)
Code
Loading syntax highlighter...

🎤 Interview Questions

Q1: What makes a function "pure"?

Answer:
  1. Same input always produces same output (deterministic)
  2. No side effects (doesn't modify external state)
  3. Doesn't depend on external mutable state

Benefits: Testable, cacheable, thread-safe, composable.

Q2: When should you prefer loops over streams?

Answer:
  • Complex control flow with early exit
  • Need to track multiple mutable variables
  • Index-based operations
  • Performance-critical code with small collections
  • Code clarity when stream chain becomes complex

📝 Summary

ConceptUse When
Pure FunctionsCalculations, transformations
ImmutabilityValue objects, DTOs, thread safety
StreamsCollection transformations, aggregations
Higher-Order FunctionsReusable cross-cutting logic
OptionalReturn values that might be absent

📅 Review Schedule for This Article

DayTaskTime
Day 1Review pure vs impure functions mental model5 min
Day 3Redo Exercise 2 (Stream Refactoring)15 min
Day 7Answer interview questions without looking10 min
Day 14Redo Debug This (Stream Side Effect)15 min
Day 30Find one impure function in your codebase to refactor20 min

Next: [Part 12: Chain of Responsibility & Pipeline]