This is a write-up of my first open-source contribution — picking an issue, tracking down the cause, fixing it, testing it, and submitting a PR — with the reasoning behind each decision along the way.
The issue
networknt/json-schema-validator is a Java library that validates JSON documents against JSON Schema. It gets real backend use — validating OpenAPI specs, for example.
The issue I picked,
#1164,
reported that {"format": "email"} validation accepts an email with a leading
non-breaking space.
A non-breaking space looks identical to the space you type with the spacebar,
but it's a different character. A regular space is U+0020 (32); a non-breaking
space is U+00A0 (160). It's the character behind HTML's , and its whole
job is to be a space that refuses to line-break.
The interesting part: put a regular space (U+0020) in the same position and the
validator correctly rejects it. To the eye, both strings look like
" name@email.com" — but depending on the character's code point, one was
rejected and one sailed through.
The issue had been open since April 2025 — more than a year. The report was short, but it came with an example input and a link to the suspect regex, which made it a good place to start, so I picked it as my first contribution.
Root cause
To find the cause I first had to reproduce the bug — and building a schema
exactly as the issue described produced no errors at all. It turns out that's
how JSON Schema works: format validation has always been optional in the
spec, and since draft 2019-09 the default is pinned down as annotation-only —
"format": "email" is informational unless you explicitly turn assertions
on. Wrong format, no error, by default.
schema.validate(input, InputFormat.JSON,
ctx -> ctx.executionConfig(cfg -> cfg.formatAssertionsEnabled(true)));
With that flag on, the issue reproduced. If you don't know about the flag you can't even reproduce the bug — which probably has something to do with why the issue sat untouched for so long.
Once it reproduced, I traced the cause. The library delegates email validation
to an EmailValidator vendored (copied in wholesale) from Apache Commons
Validator, and the regex allowing local-part characters — the part before the
@ — looks like this:
// characters allowed in the email local part
"...[^\\s" + SPECIAL_CHARS + "]";
// ^^ allow anything except whitespace and special characters
The problem is that Java's regex \s means ASCII whitespace only by default —
exactly six characters: space, tab, and four control characters (LF, VT, FF,
CR). U+0020 is in that set and gets filtered; U+00A0 is not. A character that
doesn't count as whitespace is treated like any ordinary letter, so it passes
straight through. That was the asymmetry.
While looking for a predicate to use in the fix, I found the same trap in the
standard library. Character.isWhitespace(0x00A0) returns false. Java
deliberately refuses to call a non-breaking space whitespace — by design, only
"breakable" spaces qualify — so the no-break family, non-breaking space
(U+00A0) and its relatives figure space (U+2007) and narrow no-break space
(U+202F), are all excluded. Catching those takes Character.isSpaceChar,
which follows Unicode's space classification instead. So the fix would need
both.
The fix
Where to fix it. There were two options:
- (A) Patch the vendored
EmailValidatorregex to also catch Unicode whitespace. - (B) Add a pre-check in the library's own
EmailFormatclass.
Not sure which was right, I looked at the git history first. The vendored file
had only import, cleanup, and license commits — no precedent for changing its
behavior. Past format bug fixes — time in
#1188,
uri/iri in
#983 — had all
landed in the library's own XxxFormat classes. The repo's convention was
clearly B, so I followed it.
How much to fix. My first instinct was "reject any whitespace," until I ran into this case in the official JSON-Schema-Test-Suite:
"joe bloggs"@example.com
My first reaction was that this couldn't possibly be valid — but an ASCII space
inside a quoted local part is, per spec, a perfectly legal email. "Reject any
whitespace" would have broken it. Since the bug is about non-ASCII whitespace,
not ASCII, I added ch > 0x7F to the condition so the ASCII range is never
touched. Every ASCII email that validated before validates exactly the same
after.
The final fix:
@Override
public boolean matches(ExecutionContext executionContext, String value) {
if (containsNonAsciiWhitespace(value)) {
return false;
}
return this.emailValidator.isValid(value);
}
private static boolean containsNonAsciiWhitespace(String value) {
if (value == null) {
return false;
}
return value.codePoints()
.anyMatch(ch -> ch > 0x7F
&& (Character.isWhitespace(ch) || Character.isSpaceChar(ch)));
}
Both predicates, for the reason above — isWhitespace alone misses U+00A0, the
very character from the issue. As for iterating with codePoints(): a Java
char isn't always one character; characters with high code points, like
emoji, are stored as two chars. No whitespace character lives outside the
single-char range, so it doesn't actually matter for this particular check —
but iterating by code point is the safe habit, and I followed it.
Tests
The fix appears above, but the actual work happened tests-first: a failing test is proof you've reproduced the bug, and the same test turning green is proof you've fixed it.
One problem came up while writing them. Type an actual non-breaking space into the test source, and not even the author can tell by looking whether it's really U+00A0 or just a plain space. If it were a plain space, the test would pass while verifying the wrong thing. So the source stays pure ASCII, and the character is built from its code point at runtime:
/** U+00A0 NON-BREAKING SPACE, built from its code point so no invisible character sits in the source. */
private static final String NBSP = new String(Character.toChars(0x00A0));
Before the fix, the test failed as intended.
emailWithLeadingNbspShouldFail ==> expected: <false> but was: <true>
After the fix I ran the full suite: all 8,471 tests passed. Since the suite includes the official JSON-Schema-Test-Suite, passing also means no spec-conformance regressions.
Submitting the PR
I wrote it up and submitted PR #1267 — 2 files, +85 lines in total.
In the description I noted that the sibling idn-email format has the same
bug, but that per the scope of issue #1164 this fix was limited to email.
Not much code changed, but waiting on code review always makes me a little nervous.