go backBack to blog

Why Java Needs Two Functions for What FP Does in One

Published on Feb 27 2026

Last updated on Feb 27 2026

Image by Jørgen Håland from Unsplash
No translation available.
Add translation

I’ve been revisiting Java lately for a project, after spending most of my recent time in TypeScript and more functional-leaning codebases. And immediately, one of Java’s old habits jumped back out at me: method overloading.

It’s a small detail, but it’s also a nice micro-example of why I lean more toward functional-style code than classic Java-style OOP. Java reaches for method overloading; FP and FP-ish TypeScript prefer explicit data + one function.

A simple example about printing dates perfectly illustrates why I prefer the functional style here.

The Java Way: Two Methods, Same Name

main.java

public class DatePrinter {
public static void printDate(int currDay, int currMonth, int currYear) {
System.out.print(currMonth + "/" + currDay + "/" + currYear);
}
public static void printDate(int currDay, String currMonth, int currYear) {
System.out.print(currMonth + " " + currDay + ", " + currYear);
}
public static void main(String[] args) {
printDate(30, 7, 2012);
System.out.println();
printDate(30, "July", 2012);
System.out.println();
}
}

Same name, printDate, two different parameter lists:

  • (int day, int month, int year)

  • (int day, String month, int year)

Java calls this method overloading. The compiler looks at your arguments and decides which version to call. If you pass an int for month, you get 7/30/2012. If you pass a String, you get July 30, 2012.

Technically, this is fine. It works. The program runs. All the tests pass. Life goes on.

But conceptually, this is one operation:

“Given a day, some representation of a month, and a year, print a date.”

The “one idea” is now split across multiple functions that you have to maintain separately. Want to change the formatting? Better remember to edit both. Want to search for “where do we print dates?” Enjoy hopping between overloads.

And the actual decision about “which behavior do we use?” is hidden in the compiler’s overload resolution rules, not written anywhere in your logic.

The Functional / TypeScript Style: One Function, Honest Data

Coming from JavaScript/TypeScript land, my instinct was not “make another function.” It was:

“Just let the parameter admit it can be more than one type.”

In TypeScript, that looks like this:

main.ts

type Month = number | string;
function printDate(day: number, month: Month, year: number): void {
if (typeof month === "number") {
console.log(`${month}/${day}/${year}`);
} else {
console.log(`${month} ${day}, ${year}`);
}
}
printDate(30, 7, 2012); // 7/30/2012
printDate(30, "July", 2012); // July 30, 2012

Now we have:

  • One function: printDate

  • One parameter month whose type is explicitly number | string

  • The branch is right there in the code: if (typeof month === "number") ... else ...

The function signature is honest:

“I accept either a numeric month or a string month. Here’s how I deal with each.”

Both snippets work, but they tell very different stories about where complexity should live: in the type system’s overloading rules, or in the data and the function body.

In actual functional languages (Haskell, OCaml, F#, etc.), you’d make this even more structured:

hs

data Month = Numeric Int | Named String
printDate :: Int -> Month -> Int -> String
printDate day (Numeric m) year = show m ++ "/" ++ show day ++ "/" ++ show year
printDate day (Named name) year = name ++ " " ++ show day ++ ", " ++ show year

Same idea: one function, and the variations live inside the data, not in a zoo of overloaded method signatures.

Overloading vs Explicit Data: Why I Care (And You Might Too)

You could absolutely look at this and say, “Who cares, both work.” And yeah, if this were the only thing in a codebase, no one would die.

But the pattern scales.

1. Cognitive load

With overloading, your brain has to juggle:

  • How many overloads exist?

  • Are their behaviors still consistent?

  • Which one is being called at this site?

  • What happens if I add another parameter type?

The decision is split between:

  • an IDE tooltip,

  • some compiler rules,

  • and your memory.

With explicit data + one function, the whole story lives in one place. You see:

  • all the possible shapes (number | string, or Numeric | Named)

  • all the behaviors, right next to each other.

Less “mental tabbing.” More “read this one function, understand everything it does.”

2. Discoverability

Overloads hide behind the same name:

java

printDate(); // which one are you?

You have to hover, Ctrl+click, or dig through a list of overloads.

With explicit data:

tsx

type Month = number | string;

You immediately see:

“Okay, this thing is two possibilities; somewhere we will be handling both.”

In FP languages, pattern matching on that type makes the control flow super obvious: every variation is a branch you can literally see.

3. Refactoring pain

Imagine you now want to support a third representation of month, like an enum or a proper Month object.

In Java, common reactions:

  • Add another overload:

java

public static void printDate(int day, MonthEnum month, int year) { ... }
  • Or start playing inheritance / interface gymnastics.

In a functional or TS style, you extend the type once:

ts

type Month = number | string | MonthEnum;

and then update the branching in one function. Same name, same place.

4. The deeper philosophy

This is why I think FP “feels better” here:

  • Java/OOP often expresses variation through APIs: multiple methods, overloads, inheritance, etc.

  • FP tends to express variation through data: tagged unions, algebraic data types, and simple pure functions.

One is, “Name things a lot and let the language guess which one you meant.” The other is, “Model your data honestly, then write a straightforward function over it.”

I know which one my tired little brain prefers at 11:30 PM.

I understand that, on its own, this is a tiny example, but it hints at a bigger philosophical split between mainstream OOP and functional programming.

The Bigger Picture: This Isn’t Just About Dates

It’d be easy to write this off as a cute little printDate example and move on. But the same pattern shows up everywhere in OOP-heavy code:

  • logging APIs

  • parsing utilities

  • “helper” classes

  • domain services that have eight different overloads for save, create, from, of, etc.

You end up with these wide, overloaded OO APIs where the complexity leaks into the surface area of the methods instead of being encoded in the data.

Functional style tends to push the other way:

  • Keep functions small and pure. One function, one job, no magic overload resolution happening behind your back.

  • Make data structures explicit and composable. If there are three ways to represent something, that fact lives in the type (a union/ADT), not as three scattered method signatures.

This really starts to matter when you look at:

Testing

Pure functions that take “honest” data are stupidly easy to test:

  • Call the function.

  • Assert on the return value.

  • No mocking overloads, no guessing which variant will fire.

Once behavior starts hiding behind overloading and implicit dispatch rules, you get into “uhh which path did I actually just test?” territory.

Composition

Functions that:

  • take clear input types, and

  • don’t rely on overload dispatch…

are way easier to compose and reuse.

You can map them, chain them, pass them around. They behave like building blocks. Overloaded OO APIs often behave more like vending machines: you stand in front of them and try to remember which button combination gives you the thing you want.

If you want the one-sentence version:

Java asks you to remember which overload you meant; functional style asks you to model your data properly once.

And that’s really the heart of it for me. Overloading is less “fancy polymorphism” and more “my functions are doing too much social work for my types.”

So what do we do with that, especially if we’re stuck in Java land for work or school? That’s where the conclusion comes in.

Conclusion: One Operation, One Function

In the end, the printDate example isn’t really about dates at all. It’s about where you choose to put complexity. Java’s answer is often: “in the API surface, via overloading.” Functional style’s answer is: “in the data model, via explicit types.” One gives you a menu of vaguely similar methods and a compiler that guesses which you meant; the other gives you a single, boring function that tells the truth about its inputs.

That’s why I keep gravitating toward functional patterns, even when I’m writing in “object-oriented” languages. When you model your data honestly, a lot of the need for clever overloading just evaporates. You get smaller, purer functions, easier tests, and code that behaves more like a set of Lego bricks than a row of overloaded vending machines.

I’m not pretending overloading is evil or that OOP is useless. It’s great that Java lets you write println without thinking about types every five seconds. But as codebases grow, I’d rather invest in better types than in remembering which overload does what.

Because if one operation needs three different overloads, maybe the problem isn’t your function — it’s your model of the data.

Tags:
My portrait picture

Written by Alissa Nguyen

Alissa Nguyen is a software engineer with main focus is on building better software with latest technologies and frameworks such as Remix, React, and TailwindCSS. She is currently working on some side projects, exploring her hobbies, and living with her two kitties.

Learn more about me



Built and designed by Alissa Nguyen a.k.a Tam Nguyen.

Copyright © 2026 All Rights Reserved.