Problem Statement
ESMappingAPIImpl.loadFields() decides how to serialize a field by its storage column
(field_contentlet) rather than its field type. When a TextField is backed by an integerN
or floatN column — which dotCMS allows and which exists in real content types — the String value
is handed to DecimalFormat.format() and throws:
WARN business.ESMappingAPIImpl - Error indexing field: viewWeekTotal of contentlet: 3c3276a0-33d2-4c5e-80a8-09edeb11572f
java.lang.IllegalArgumentException: Cannot format given Object as a Number
at java.base/java.text.DecimalFormat.format(DecimalFormat.java:584)
at java.base/java.text.Format.format(Format.java:165)
at com.dotcms.content.elasticsearch.business.ESMappingAPIImpl.loadFields(ESMappingAPIImpl.java:1118)
at com.dotcms.content.elasticsearch.business.ESMappingAPIImpl.toMap(ESMappingAPIImpl.java:465)
at com.dotcms.content.elasticsearch.business.ContentletIndexAPIImpl.addBulkRequest(ContentletIndexAPIImpl.java:2785)
The affected fields:
| Content Type |
Field |
field_type |
field_contentlet |
RptTopHits |
viewWeekTotal |
com.dotcms.contenttype.model.field.TextField |
integer1 |
Trending |
viewWeekTotal |
com.dotcms.contenttype.model.field.TextField |
integer1 |
Stored value: {"type": "Text", "value": "54"} — a String, correctly matching the declared
TextField. But the branch is chosen by the column name:
// ESMappingAPIImpl.java:1114
} else if (field.getFieldContentlet()
.startsWith(ESMappingConstants.FIELD_ELASTIC_TYPE_FLOAT) || field
.getFieldContentlet()
.startsWith(ESMappingConstants.FIELD_ELASTIC_TYPE_INTEGER)) {
contentletMap.put(keyName, valueObj);
contentletMap.put(keyNameText, numFormatter.format(valueObj)); // String -> IllegalArgumentException
}
integer1 starts with integer, so the numeric branch is taken for a field whose type and value
are both textual.
The error handling contradicts itself
// ESMappingAPIImpl.java:1141
} catch (Exception e) {
Logger.warn(ESMappingAPIImpl.class, "Error indexing field: " + field.getFieldName()
+ " of contentlet: " + contentlet.getInode(), e);
throw new DotDataException(e.getMessage(), e);
}
It logs at WARN — the level that says "recoverable, carry on" — and then rethrows, aborting the
entire contentlet. One misconfigured field out of dozens makes the whole document unindexable. The
adjacent Date branch a few lines above does the opposite and degrades gracefully:
} catch(Exception ex) {
contentletMap.put(keyName, valueObj);
contentletMap.put(keyNameText, valueObj.toString());
}
A valueObj.toString() fallback here would have indexed "54" correctly, since that is what the
field declares itself to be.
How it surfaced
Found while reindexing 370 contentlets via POST /api/v1/content/_bulkrefresh: 364 succeeded, 6
failed — 4 from an unrelated serialization limit and these 2. Both are RptTopHits/Trending
content, so any reindex touching those types loses those documents. The failure is silent from the
operator's side: the UI reports a flat failure count with no field attribution (see the companion
issue on per-engine and per-document failure visibility).
Steps to Reproduce
-
Create a content type with a Text field whose backing column is numeric. On an existing
install this can be confirmed with:
SELECT st.velocity_var_name AS content_type, f.velocity_var_name AS field,
f.field_type, f.field_contentlet
FROM field f JOIN structure st ON st.inode = f.structure_inode
WHERE f.field_type LIKE '%TextField'
AND (f.field_contentlet LIKE 'integer%' OR f.field_contentlet LIKE 'float%');
-
Save a contentlet with a textual value in that field (e.g. "54").
-
Reindex it — full reindex, POST /api/v1/esindex/reindex?contentType=<type>, or
POST /api/v1/content/_bulkrefresh.
-
Observe Cannot format given Object as a Number and the contentlet absent from the index.
-
Note the value is a perfectly valid TextField value; nothing in the UI flags the field or
the content type as invalid.
Acceptance Criteria
dotCMS Version
dotcms/dotcms:trunk. PostgreSQL 16, ~1.55 M contentlets, OpenSearch 1.3.20 + 3.4.0 in
PHASE_1_DUAL_WRITE_ES_READS.
Severity
Medium - Some functionality impacted
Medium — affects only content types with this modelling, but for those every document is silently
missing from the index, and the cause is not discoverable from the UI.
Links
ESMappingAPIImpl.java:1114 — branch selected by field_contentlet instead of field type
ESMappingAPIImpl.java:1118 — numFormatter.format(valueObj)
ESMappingAPIImpl.java:1141 — WARN followed by rethrow
ESMappingAPIImpl.java:1121 — the Date branch that degrades gracefully instead
Freshdesk ticket: NA — found during internal ES→OpenSearch migration testing.
Problem Statement
ESMappingAPIImpl.loadFields()decides how to serialize a field by its storage column(
field_contentlet) rather than its field type. When aTextFieldis backed by anintegerNor
floatNcolumn — which dotCMS allows and which exists in real content types — the String valueis handed to
DecimalFormat.format()and throws:The affected fields:
field_typefield_contentletRptTopHitsviewWeekTotalcom.dotcms.contenttype.model.field.TextFieldinteger1TrendingviewWeekTotalcom.dotcms.contenttype.model.field.TextFieldinteger1Stored value:
{"type": "Text", "value": "54"}— a String, correctly matching the declaredTextField. But the branch is chosen by the column name:integer1starts withinteger, so the numeric branch is taken for a field whose type and valueare both textual.
The error handling contradicts itself
It logs at WARN — the level that says "recoverable, carry on" — and then rethrows, aborting the
entire contentlet. One misconfigured field out of dozens makes the whole document unindexable. The
adjacent
Datebranch a few lines above does the opposite and degrades gracefully:A
valueObj.toString()fallback here would have indexed"54"correctly, since that is what thefield declares itself to be.
How it surfaced
Found while reindexing 370 contentlets via
POST /api/v1/content/_bulkrefresh: 364 succeeded, 6failed — 4 from an unrelated serialization limit and these 2. Both are
RptTopHits/Trendingcontent, so any reindex touching those types loses those documents. The failure is silent from the
operator's side: the UI reports a flat failure count with no field attribution (see the companion
issue on per-engine and per-document failure visibility).
Steps to Reproduce
Create a content type with a Text field whose backing column is numeric. On an existing
install this can be confirmed with:
Save a contentlet with a textual value in that field (e.g.
"54").Reindex it — full reindex,
POST /api/v1/esindex/reindex?contentType=<type>, orPOST /api/v1/content/_bulkrefresh.Observe
Cannot format given Object as a Numberand the contentlet absent from the index.Note the value is a perfectly valid
TextFieldvalue; nothing in the UI flags the field orthe content type as invalid.
Acceptance Criteria
loadFields()selects the serialization branch by field type, not by storage column;or the numeric branch tolerates non-numeric values.
indexed via
toString()) and the rest of the document is indexed, consistent with how theDatebranch already behaves.contentlet, so the content-type misconfiguration is actionable.
modelled in the first place.
dotCMS Version
dotcms/dotcms:trunk. PostgreSQL 16, ~1.55 M contentlets, OpenSearch 1.3.20 + 3.4.0 inPHASE_1_DUAL_WRITE_ES_READS.Severity
Medium - Some functionality impacted
Medium — affects only content types with this modelling, but for those every document is silently
missing from the index, and the cause is not discoverable from the UI.
Links
ESMappingAPIImpl.java:1114— branch selected byfield_contentletinstead of field typeESMappingAPIImpl.java:1118—numFormatter.format(valueObj)ESMappingAPIImpl.java:1141— WARN followed by rethrowESMappingAPIImpl.java:1121— theDatebranch that degrades gracefully insteadFreshdesk ticket: NA — found during internal ES→OpenSearch migration testing.