Skip to content

WW-5685 Stop dropping the rest of a -conversion.properties file after the first already-mapped key - #1860

Merged
lukaszlenart merged 1 commit into
mainfrom
WW-5685-conversion-file-processor-break
Aug 23, 2026
Merged

WW-5685 Stop dropping the rest of a -conversion.properties file after the first already-mapped key#1860
lukaszlenart merged 1 commit into
mainfrom
WW-5685-conversion-file-processor-break

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5685

DefaultConversionFileProcessor.process skipped keys already present in the converter mapping with a break rather than a continue, so the first already-mapped entry ended the loop and every remaining entry in that -conversion.properties file was silently dropped — no warning, no error, the affected properties simply fell back to default conversion.

 if (mapping.containsKey(key)) {
-    break;
+    continue;
 }

Why it triggers in practice

XWorkConverter.buildConverterMapping walks the class, then its interfaces, then its superclass, passing one accumulating mapping into each addConverterMapping call. So a key claimed earlier in that walk aborts a later file outright:

  • subclass Foo-conversion.properties declares bar
  • superclass FooBase-conversion.properties declares bar, baz, qux
  • when the superclass file is read, bar is already mapped → baz and qux are never registered

Annotation-derived entries land in the same map, so an @TypeConversion on the class could abort its properties file the same way.

Properties extends Hashtable and entrySet() has no defined order, so which entries survived depended on hash order rather than file order — which is what makes this hard to diagnose from the symptom.

WW-3871 fixed the identical defect in the annotation path (XWorkConverter, #1812, shipped 7.3.0). This is the properties-file path that change did not touch. The remaining break at XWorkConverter:761 is unrelated and correct — it exits an interface search after a hit.

Testing — and a vacuous test caught by mutation

DefaultConversionFileProcessorTest (new). The ticket suggested a fixture "whose first key is already mapped", but Hashtable ordering means no fixture can pin down which key is read first. So the test derives the colliding key from the actual iteration order at run time and pre-maps that one:

String firstKey = (String) fixture.entrySet().iterator().next().getKey();
mapping.put(firstKey, SENTINEL);
processor.process(mapping, PropertiesCollisionBaseAction.class, FILENAME);
// every key in the file must now be present

With break, the loop stops on that first entry and registers nothing at all — discriminating on any JDK, in any hash order, with no reliance on Hashtable layout. A second test asserts every entry registers when nothing is pre-mapped, guarding against a fix that skips too much.

XWorkConverterTest.testPropertiesEntriesAfterAKeyCollisionAreStillRegistered covers the realistic hierarchy trigger. Worth flagging: the first version of this test was vacuous. Its shared key hashed to the last position, so break dropped nothing and it passed against the unfixed code — only the mutation check exposed it. The key is renamed so it is read first (verified identical on Temurin 17.0.14, 21.0.7 and 25.0.1), and the test now asserts that precondition explicitly:

assertEquals("the fixture only discriminates while the shared key is read first; "
                + "Properties iteration order has changed and it must be renamed again",
        "CreateIfNull_overridden", firstKeyOf(PROPERTIES_COLLISION_BASE_FILE));

so a future reordering fails loudly instead of going quiet.

Both tests were mutation-checked by reverting continue to break; both fail.

mvn test -DskipAssembly -pl core — 3193 tests, 0 failures, 0 errors.

Compatibility

Behaviour changes as intended: entries previously dropped now register. That matches what WW-3871 shipped for the annotation path in 7.3.0. The containsKey precedence rule itself is untouched — first source to claim a key still wins, so nothing that already worked is overridden. Worth a Version Notes line; no Migration Guide entry needed.

🤖 Generated with Claude Code

…e file

DefaultConversionFileProcessor.process skipped keys already present in the
converter mapping with a break rather than a continue, so the first
already-mapped entry ended the loop and every remaining entry in that
-conversion.properties file was silently dropped. No warning, no error;
the affected properties just fell back to default conversion.

XWorkConverter.buildConverterMapping walks the class, then its interfaces,
then its superclass, passing one accumulating mapping into each call, so a
key claimed earlier in that walk aborted a later file outright. Annotation
derived entries land in the same map and could abort a file the same way.

Properties extends Hashtable and entrySet() has no defined order, so which
entries survived depended on hash order rather than file order.

WW-3871 fixed the same defect in the annotation path (PR #1812, 7.3.0);
this is the properties-file path that change did not touch.

Two tests, both mutation-checked by reverting continue to break:

DefaultConversionFileProcessorTest derives the colliding key from the
actual iteration order at run time and pre-maps whichever key comes first,
so break registers nothing at all. That makes it discriminating on any JDK
in any hash order, rather than depending on where a fixture's colliding key
happens to land.

XWorkConverterTest covers the realistic trigger through the hierarchy walk,
a subclass and superclass file sharing a key. This one does depend on the
fixture's iteration order, and the first version of it was vacuous -- the
shared key landed last, so break dropped nothing and the test passed
against the unfixed code. The key is now named so that it is read first
(verified identical on Temurin 17, 21 and 25), and the test asserts that
precondition, so a future reordering fails loudly instead of going quiet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@lukaszlenart
lukaszlenart merged commit b8aa6cb into main Aug 23, 2026
13 checks passed
@lukaszlenart
lukaszlenart deleted the WW-5685-conversion-file-processor-break branch August 23, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant