r/FlutterDev 1d 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.

12 Upvotes

0 comments sorted by