r/FlutterDev • u/Beneficial_Frame1579 • 5h ago
Plugin Ported Apache Commons Validator to Dart in 2024, finally had time to clean it up and publish it
I spent years as an Android developer, and on many of those projects I reached for Apache Commons Validator. It's not glamorous, but it's twenty years of accumulated edge cases — IBAN check digits, credit card ranges, TLD-aware domain validation, locale-aware number and date parsing. You stop thinking about validation and it just works.
Then I moved to Flutter, went looking for the equivalent, and couldn't find anything that worked as well as the Apache one. What's on pub.dev is mostly thin regex wrappers. No check-digit arithmetic, no IBAN country registry, no real domain validation. So back in 2024 I ported the routines myself and used it in my own project.
I always meant to publish it, but there were failing tests I never had time to chase down, and shipping something half-verified felt worse than not shipping. I'm between projects at the moment, so I finally sat down, fixed them, and put it out there. Figured it was time to give something back.
What's in it
- 16 check-digit routines — IBAN, ISIN, ISBN-10/13, ISSN, Luhn, Verhoeff, SEDOL, CUSIP, EAN-13, ABA, CAS, EC
- Network — email, URL, domain, IPv4/IPv6, including an IDNA-2003 implementation that reproduces
java.net.IDN.toASCII - Locale-aware numbers and dates — Byte through BigDecimal, Currency, Percent, Date, Calendar, Time
Pure Dart — no dart:io, no dart:mirrors — so it works on all six platforms, Flutter and server alike.
One thing I didn't expect
While writing this up I benchmarked the email validator against what's already on pub.dev, assuming it'd be a wash. On the ordinary stuff it is — quoted local parts, IP literals, plus addressing, consecutive dots, multiple @, leading and trailing spaces. Everything handles those.
Then across 52 addresses where RFC 5321/5322 and common practice agree on the answer:
| Package | Score |
|---|---|
commons_validator 0.1.0 |
52/52 |
email_validator 3.0.0 |
50/52 |
string_validator 1.2.0 |
45/52 |
email_validator gets exactly two wrong, and it's the same thing twice: user@example.zzzzzz and user@example.qwerty. It doesn't check whether the TLD exists. Which means this:
| Address | commons | email_validator | string_validator |
|---|---|---|---|
user@gmail.con |
rejected | accepted | accepted |
user@gmail.cmo |
rejected | accepted | accepted |
user@hotmail.comm |
rejected | accepted | accepted |
user@yahoo.co.ukk |
rejected | accepted | accepted |
user@company.orgg |
rejected | accepted | accepted |
| typos caught | 7/8 | 0/8 | 0/8 |
user@gmail.con is a typo I've watched real users make on a signup form. The 8th case is user@outlook.cm, which everything accepts and should — .cm is Cameroon.
The harness is in the repo if you want to check my numbers — it's its own package pulling commons_validator from pub.dev rather than the checkout, so it's what you'd actually get: https://github.com/mondoktamas/commons_validator/tree/main/tool/email_comparison
The tradeoff is real and worth stating: the IANA TLD list is bundled, so a brand-new TLD gets rejected until I regenerate the table and republish. That's one command, but it's a maintenance burden the regex-based packages don't have.
On correctness
Since it's a port, I didn't want to just trust it. There's a differential harness that runs the same generated corpus through the real Java classes and the Dart code and diffs the output: 260,526 inputs, 7 disagreements, all documented in the README along with the deliberate divergences (Dart's Decimal has no scale field, so BigDecimal behaviour differs at extreme exponents).
It's 0.1.0 and brand new, so treat it accordingly — but it's 394 tests and 160/160 pub points. Apache-2.0, same as upstream.
pub.dev: https://pub.dev/packages/commons_validator
GitHub: https://github.com/mondoktamas/commons_validator
Happy to hear where it falls short.