Skip to content

Error Handling & Diagnostics

← Custom Values | Next: Annotation Reference →


See also:


The codec uses EMF's standard diagnostic mechanism for reporting errors and warnings during serialization and deserialization.

0. Validation Layers

Validation happens at four distinct layers, each with different timing and scope:

LayerWhenWhatWhere Implemented
1. Annotation ParsingEPackage registrationInvalid annotation keys, wrong level, invalid valuescodec.metadata (AspectProvider)
2. Config ResolutionFirst access to configSelf-contained constraints (e.g., nameKey with PLAIN format)Config.validate() methods
3. Cross-Config ValidationConfig resolutionMulti-config constraints (e.g., STRUCTURED + NONE + superType)ConfigurationResolver.validateCrossConfig()
4. Runtime (Ser/Deser)During operationData-dependent errors (e.g., CLASS strategy + null instanceClassName)Serializer/Deserializer entries

Layer 1: Annotation Parsing

Occurs when an EPackage is registered with the MetadataService. Validates:

  • Annotation keys are at correct level (e.g., typeMapId only on EClass, not EReference)
  • Enum values are valid
  • Boolean values are valid

Implementation: CodecAspectProvider.checkForClassOnlyKeys() in codec.metadata project.

See Section 6.10 for error scenarios.

Layer 2: Config Resolution (Self-Contained)

Occurs when configuration is first resolved for an EClass/EStructuralFeature. Each Config.validate() method checks:

  • Properties that are ignored in certain modes (e.g., typeNameKey ignored when format is PLAIN)
  • Property dependencies within the same config

Layer 3: Cross-Config Validation

Occurs after config resolution when multiple configs interact. Examples:

  • STRUCTURED + NONE + superTypeSerialize=true → ERROR (can't write supertype in _type object when no _type)
  • STRUCTURED + both value readers → WARNING (superType reader ignored)
  • STRUCTURED + both value writers → WARNING (superType writer ignored)

Layer 4: Runtime (Serialization/Deserialization)

Occurs during actual serialization or deserialization. Examples:

  • CLASS strategy + instanceClassName is null → ERROR
  • Abstract type without concrete resolution → ERROR
  • Unknown discriminator value → depends on fallbackStrategy

See Section 6 for comprehensive error scenarios.


1. Principles

  • All errors and warnings are collected in the EMF Resource's diagnostics (resource.getErrors(), resource.getWarnings())
  • Errors cause the load/save operation to fail after all diagnostics are collected (unless fail-fast mode)
  • Warnings do not cause failure but are reported for user awareness
  • All diagnostics are also logged via the standard logging mechanism
  • The DeserializationMode setting affects how strictly errors are enforced

2. Error Severity

SeverityBehaviorExamples
ERROROperation fails, diagnostic addedCannot instantiate abstract type, unresolved type URI, missing required type info
WARNINGOperation continues, diagnostic addedType collision (content type differs from hint), deprecated option usage, unknown field

2.1 DeserializationMode Impact

The DeserializationMode setting (see Load/Save Options section 5) affects error severity:

ScenarioSTRICT ModeLENIENT Mode
Unknown field in JSONERRORWARNING (field skipped)
Type mismatchERRORWARNING (best-effort conversion)
Missing required fieldERRORWARNING (default used)
Value conversion failureERRORWARNING (default used)

3. Diagnostic Information

Each diagnostic includes:

  • Message - Description of the issue
  • Location - Resource URI, line/column if available
  • Source - Codec component that raised the issue (for programmatic filtering)

4. Option Keys

4.1 Diagnostic Control Options

Java ConstantProperty KeyValue TypeDefaultDescription
CODEC_FAIL_FASTcodec.failFastBooleanfalseThrow on first error instead of collecting all
CODEC_SUPPRESS_WARNINGScodec.suppressWarningsBooleanfalseSuppress all warnings
CODEC_SUPPRESS_WARNING_SOURCEScodec.suppressWarningSourcesSet<String>emptySuppress warnings from specific sources
CODEC_DIAGNOSTIC_HANDLERcodec.diagnosticHandlerDiagnosticHandlernullCustom diagnostic handler

4.2 Usage Examples

Basic diagnostic checking:

java
resource.load(inputStream, options);

// Check for errors
for (Diagnostic error : resource.getErrors()) {
    System.err.println("ERROR: " + error.getMessage());
}

// Check for warnings
for (Diagnostic warning : resource.getWarnings()) {
    System.out.println("WARNING: " + warning.getMessage());
}

Fail-fast mode:

java
Map<String, Object> options = Map.of(
    "codec.failFast", true
);

try {
    resource.load(inputStream, options);
} catch (CodecException e) {
    // First error encountered - operation aborted
    System.err.println("Failed: " + e.getMessage());
}

Suppress warnings:

java
Map<String, Object> options = Map.of(
    // Suppress all warnings
    "codec.suppressWarnings", true
);
resource.load(inputStream, options);
// resource.getWarnings() will be empty

Suppress specific warning sources:

java
Map<String, Object> options = Map.of(
    "codec.suppressWarningSources", Set.of(
        "TypeDeserializationEntry",
        "AttributeDeserializationEntry"
    )
);
resource.load(inputStream, options);
// Warnings from these sources are filtered out

5. Diagnostic Sources

Each diagnostic includes a source identifier for programmatic handling.

5.1 Deserialization Sources

SourceComponentError ExamplesWarning Examples
CodecEObjectDeserializerRoot deserializerNo type info and no hint, failed EObject creationUnexpected token
TypeDeserializationEntryType resolutionCould not resolve EClass, unexpected token
IdDeserializationEntryID parsingEObject not yet createdSTRUCTURED ID format mismatch, parse errors
ReferenceDeserializationEntryReference handlingEObject not yet created, no deserializer foundUnexpected token
AttributeDeserializationEntryAttribute parsingEObject not yet createdValue conversion failure, unexpected token
CodecValueReader (custom)Custom value readingInvalid value format, parse failureDeprecated format, data migration hints

5.2 Serialization Sources

SourceComponentError ExamplesWarning Examples
CodecEObjectSerializerRoot serializerNull EObject, unregistered EPackage
TypeSerializationEntryType writingUnknown type strategy
IdSerializationEntryID writingMissing ID feature, null ID valueCombined ID with null component
ReferenceSerializationEntryReference writingCircular reference (non-expand), cross-doc without URIUnresolved proxy serialized
AttributeSerializationEntryAttribute writingValue conversion failureEnum value not found
CodecValueWriter (custom)Custom value writingCannot serialize value, null valueLossy conversion, precision loss

5.3 Common Sources (Both Directions)

SourceComponentError ExamplesWarning Examples
CodecResourceResource operationsI/O failure, stream errorUnexpected token
ConfigurationResolverConfig resolutionInvalid option type, unknown enum valueDeprecated option usage

5.4 Programmatic Source Detection

java
for (Diagnostic error : resource.getErrors()) {
    String source = error.getSource();

    switch (source) {
        // Deserialization
        case "TypeDeserializationEntry" -> handleTypeError(error);
        case "IdDeserializationEntry" -> handleIdError(error);
        case "ReferenceDeserializationEntry" -> handleRefError(error);
        case "AttributeDeserializationEntry" -> handleAttrError(error);
        // Serialization
        case "TypeSerializationEntry" -> handleTypeError(error);
        case "IdSerializationEntry" -> handleIdError(error);
        case "ReferenceSerializationEntry" -> handleRefError(error);
        case "AttributeSerializationEntry" -> handleAttrError(error);
        // Custom value handlers
        case String s when s.startsWith("CodecValue") -> handleCustomValueError(error);
        default -> handleGenericError(error);
    }
}

6. Complete Error Scenarios

Deserialization Errors

6.1 Type Resolution Errors (Deserialization)

ScenarioSeverityMessage TemplateRecovery
No type info and no hintERRORCannot deserialize: no type information and no CODEC_ROOT_TYPE hintOperation fails
Unknown type value (with hint)WARNINGCould not resolve EClass from type value: {value}Falls back to hint
Unknown type value (no hint)ERRORCannot deserialize: no type information and no CODEC_ROOT_TYPE hintOperation fails
Abstract type without concrete hintERRORCannot instantiate abstract type: {type}Operation fails
Type collision (incompatible)WARNINGType collision: CODEC_ROOT_TYPE={hint} but content type={actual}Content type used

6.2 ID Errors (Deserialization)

ScenarioSeverityMessage TemplateRecovery
Missing ID attributeWARNINGNo ID attribute found for EClass {class}ID not set
Invalid separator in combined IDWARNINGCannot split ID value '{value}' with separator '{sep}'Partial ID set
STRUCTURED format mismatchWARNINGExpected STRUCTURED ID format but found {actual}Best-effort parse
ID feature not foundWARNINGID feature '{feature}' not found in EClass {class}Feature skipped

6.3 Reference Errors (Deserialization)

ScenarioSeverityMessage TemplateRecovery
Circular reference in expandWARNINGCircular reference detected during expand: {path}Reference skipped
Unresolved proxy URIWARNINGCannot resolve proxy URI: {uri}Proxy created
Invalid reference formatWARNINGExpected {expected} reference format but found {actual}Best-effort parse
Missing ref keyWARNINGReference object missing '_ref' keyReference skipped

6.4 Feature Errors (Deserialization)

ScenarioSTRICTLENIENTMessage TemplateRecovery
Unknown feature in JSONERRORWARNINGUnknown feature '{name}' for EClass {class}Field skipped
Value conversion failureERRORWARNINGCannot convert value '{value}' to type {type}Default used
Required feature missingERRORWARNINGRequired feature '{name}' not found in JSONDefault used
Type mismatchERRORWARNINGExpected {expected} but found {actual} for '{name}'Best-effort conversion

Serialization Errors

6.5 Type Serialization Errors

ScenarioSeverityMessage TemplateRecovery
Null EObjectERRORCannot serialize null EObjectOperation fails
Unregistered EPackageERROREPackage not registered: {nsURI}Operation fails
Unknown type strategyERRORUnknown TypeStrategy: {strategy}Operation fails

6.6 ID Serialization Errors

ScenarioSeverityMessage TemplateRecovery
Missing ID featureWARNINGID feature '{feature}' not found in EClass {class}ID field skipped
Null ID valueWARNINGID value is null for feature '{feature}'ID field skipped
Combined ID with null componentWARNINGCombined ID component '{feature}' is nullComponent omitted

6.7 Reference Serialization Errors

ScenarioSeverityMessage TemplateRecovery
Circular reference (expand mode)WARNINGCircular reference detected: {path}Written as reference, not expanded
Cross-document without URIERRORCannot serialize cross-document reference: target has no URIOperation fails
Unresolved proxyWARNINGSerializing unresolved proxy: {uri}Proxy URI written
Null reference in required featureWARNINGRequired reference '{feature}' is nullField skipped

6.8 Feature Serialization Errors

ScenarioSeverityMessage TemplateRecovery
Value conversion failureERRORCannot convert value '{value}' to JSONOperation fails
Enum value not foundWARNINGEnum literal '{literal}' not found in {enum}Literal name used
Unsupported attribute typeERRORCannot serialize attribute type: {type}Operation fails

Common Errors (Both Directions)

6.9 Configuration Errors

ScenarioSeverityMessage TemplateRecovery
Invalid discriminator pathERRORInvalid discriminator path: {path}Operation fails
Unknown discriminator valueWARNINGUnknown discriminator value '{value}' for map '{mapId}'Falls back to type strategy
Invalid scope valueERRORInvalid StrategyScope value: {value}Operation fails
Invalid option typeERRORExpected {expected} for option '{key}' but got {actual}Operation fails

6.10 Annotation Parsing Errors (Metadata Layer)

These errors occur during EPackage registration when the codec.metadata layer parses EAnnotations into Aspect objects. The metadata layer validates annotations and ensures only valid configurations are stored in Aspect objects.

Key principle: Aspect objects always contain valid configurations. Invalid annotations are ignored (not applied) and logged as diagnostics.

ScenarioSeverityMessage TemplateRecovery
Annotation key at wrong levelWARNINGAnnotation key '{key}' is not valid on {elementType}, ignoredKey ignored, not applied to Aspect
Unknown annotation keyWARNINGUnknown annotation key '{key}' on {element}Key ignored
Invalid enum valueWARNINGInvalid value '{value}' for enum {enumType}, using defaultDefault value used
Invalid boolean valueWARNINGInvalid boolean value '{value}' for key '{key}'Default value used

Examples:

WARNING: Annotation key 'typeMapId' is not valid on EReference, ignored
WARNING: Annotation key 'typeDiscriminatorPath' is not valid on EReference, ignored
WARNING: Annotation key 'idStrategy' is not valid on EAttribute, ignored
WARNING: Unknown annotation key 'fooBar' on EClass 'Person'

Diagnostic Collection Pattern:

Diagnostics are collected hierarchically in the metadata model. Each metadata element owns its diagnostics (the container identifies the source), with derived allDiagnostics features for convenient aggregation:

PackageMetadata
  ├── diagnostics: EList<MetadataDiagnostic>     [own package-level issues]
  └── allDiagnostics: EList<MetadataDiagnostic>  [derived] = own + all class diagnostics

      └── ClassMetadata
            ├── diagnostics: EList<MetadataDiagnostic>     [own class-level issues]
            └── allDiagnostics: EList<MetadataDiagnostic>  [derived] = own + all feature diagnostics

                  └── FeatureMetadata
                        └── diagnostics: EList<MetadataDiagnostic>  [own feature-level issues]

Interface: DiagnosticContainer provides the diagnostics containment reference, implemented by PackageMetadata, ClassMetadata, and FeatureMetadata.

Accessing diagnostics:

java
MetadataService metadataService = ...;
PackageMetadata metadata = metadataService.getPackageMetadata(myPackage);

// Get all diagnostics for entire package (including all classes and features)
for (MetadataDiagnostic diagnostic : metadata.getAllDiagnostics()) {
    System.out.println(diagnostic.getSeverity() + ": " + diagnostic.getMessage());
    // Container of diagnostic identifies the source element
    EObject source = diagnostic.eContainer();
}

// Get diagnostics for a specific class (including its features)
ClassMetadata classMetadata = metadata.getClasses().get(0);
for (MetadataDiagnostic diagnostic : classMetadata.getAllDiagnostics()) {
    // ...
}

// Get only direct diagnostics for a feature
FeatureMetadata featureMetadata = classMetadata.getFeatures().get(0);
for (MetadataDiagnostic diagnostic : featureMetadata.getDiagnostics()) {
    // ...
}

Implementation: See Annotation Reference for which keys are valid at each EMF element level (EClass, EReference, EAttribute).

6.11 Custom Value Reader/Writer Errors

Custom value readers and writers (see Custom Values) can report diagnostics via their context. The source name defaults to the reader/writer's getName() value.

ScenarioSeverityMessage TemplateRecovery
Invalid value formatERRORCannot parse value: {details}Operation fails (or default in LENIENT)
Value out of rangeERRORValue {value} out of valid rangeOperation fails (or clamped in LENIENT)
Deprecated format detectedWARNINGDeprecated format, consider migrating to {newFormat}Value parsed with warning
Precision lossWARNINGPrecision loss converting {source} to {target}Best-effort conversion
Missing optional dataWARNINGOptional field '{field}' not presentDefault used

Example - Custom reader reporting diagnostics:

java
public class GeoCoordinateReader implements CodecValueReader<GeoCoordinate, EAttribute> {
    @Override
    public String getName() { return "geoCoordinate"; }

    @Override
    public GeoCoordinate read(CodecReaderContext ctx, EAttribute feature) throws IOException {
        JsonParser parser = ctx.getParser();

        // Validate coordinates
        double lat = parser.getDoubleValue();
        if (lat < -90 || lat > 90) {
            ctx.addError("Latitude " + lat + " out of valid range [-90, 90]");
            return null;
        }

        // Warn about precision
        if (hasExcessivePrecision(lat)) {
            ctx.addWarning("Coordinate precision exceeds 6 decimal places, truncating");
        }

        return new GeoCoordinate(lat, lon);
    }
}

7. Diagnostic API

7.1 Accessing Diagnostic Details

java
resource.load(inputStream, options);

for (Diagnostic error : resource.getErrors()) {
    System.err.println("ERROR: " + error.getMessage());

    // Access location if available
    if (error instanceof ResourceDiagnostic rd) {
        System.err.println("  Location: " + rd.getLocation());
        System.err.println("  Line: " + rd.getLine() + ", Column: " + rd.getColumn());
    }
}

7.2 Custom Diagnostic Handler

For advanced use cases (logging, metrics, alerting), provide a custom handler:

java
DiagnosticHandler handler = new DiagnosticHandler() {
    @Override
    public void handleError(String message, String source, Object location) {
        logger.error("[{}] {}", source, message);
        metrics.incrementCounter("codec.errors." + source);
    }

    @Override
    public void handleWarning(String message, String source, Object location) {
        logger.warn("[{}] {}", source, message);
        metrics.incrementCounter("codec.warnings." + source);
    }
};

Map<String, Object> options = Map.of(
    "codec.diagnosticHandler", handler
);
resource.load(inputStream, options);

8. Implementation Details

The codec uses a DiagnosticCollector internally to aggregate errors and warnings:

  1. Initialization: A DiagnosticCollector is created at the start of each load/save operation
  2. Propagation: The collector is passed through Jackson's context attributes
  3. Collection: Each deserialization/serialization entry adds diagnostics via ContextHelper.addError()/addWarning()
  4. Finalization: After the operation, collector.addToResource(resource) transfers all diagnostics to the EMF Resource

8.1 Null-Safety

All diagnostic methods are null-safe. When context is null (e.g., in unit tests), diagnostics are silently skipped:

java
// Safe to call even if ctxt is null
ContextHelper.addWarning(ctxt, "message", parser, "Source");

8.2 Custom Value Reader/Writer Integration

Custom value readers and writers can report diagnostics via their context objects:

java
public class MyValueReader implements CodecValueReader<MyType, EAttribute> {
    @Override
    public MyType read(CodecReaderContext ctx, EAttribute feature) throws IOException {
        // Report warning via context
        ctx.addWarning("Deprecated format detected, consider migrating");

        // Report error (will be collected, operation continues unless fail-fast)
        ctx.addError("Invalid value format");

        return parseValue(ctx.getParser());
    }
}

See Custom Values (section 4) for complete context API.


9. Security Limits

The codec enforces built-in limits to protect against denial-of-service attacks via malicious input.

9.1 Nesting Depth Limit

PropertyValue
ConstantCodecEObjectDeserializer.MAX_NESTING_DEPTH
Default200
ScopeAll recursive value reading during deserialization
Recoveryparser.skipChildren(), value dropped, WARNING diagnostic added

Deeply nested JSON/YAML/CBOR structures (objects within objects, arrays within arrays, or mixed) are truncated at 200 levels of nesting. When the limit is exceeded:

  1. The remaining nested content is skipped via parser.skipChildren() to keep the parser in a consistent state
  2. The value is dropped (null)
  3. A warning diagnostic with source CodecEObjectDeserializer or AttributeDeserializationEntry is added to the resource
  4. Deserialization continues — the EObject is still produced, only the deeply nested value is missing

The limit protects two deserialization paths:

  • Deferred properties — values appearing before _type in JSON, read by CodecEObjectDeserializer.readCurrentValue()
  • EJavaObject / JSON-to-String attributes — values read by AttributeDeserializationEntry.readAnyJsonValue() and readJsonStructureAsString()

CWE references: CWE-674 (Uncontrolled Recursion), CWE-400 (Resource Exhaustion).

9.2 Collection Size Limit

PropertyValue
ConstantCodecEObjectDeserializer.MAX_COLLECTION_SIZE
Default100,000
ScopeAll collection-accumulating loops during recursive value reading
RecoveryRemaining elements skipped, WARNING diagnostic added, truncated collection returned

Arrays and objects with more than 100,000 elements are truncated during deserialization. When the limit is exceeded:

  1. A warning diagnostic is emitted once
  2. Remaining elements are skipped via parser.skipChildren() without accumulating
  3. The loop drains to the matching END_ARRAY/END_OBJECT
  4. The truncated collection (with elements read so far) is returned

The limit applies to the same paths as the nesting depth limit (deferred properties and EJavaObject attributes).

CWE references: CWE-400 (Resource Exhaustion), CWE-770 (Allocation Without Limits).

9.3 Type Resolution Scoping

PropertyValue
Strategies affectedNAME, CLASS, NUMERIC
RequirementSchema hint (CODEC_ROOT_SCHEMA or CODEC_ROOT_TYPE)
ScopeTypeResolutionHelper, TypeDeserializationEntry
RecoveryResolution returns null, warning diagnostic added to resource

Non-URI type strategies resolve type values within a scoped context package derived from the schema hint. Without a schema hint, resolution fails instead of scanning all registered EPackages. This prevents type confusion when multiple packages define classes with the same name or overlapping classifier IDs.

The URI strategy (default) is not affected — full URIs are always unambiguous on the nsURI; version selection under same-nsURI multi-version is a separate step (below).

Bounded candidate query ≠ global scan (B.5/A.3). Selecting the version of a known nsURI (via getPackageMetadataVersions(nsURI), see 06 §6.4.6) is a lookup scoped to that one nsURI — it is not the forbidden global cross-package scan. When that scoped query returns more than one version and no codec.rootFingerprint/pin disambiguates, resolution fails with an error listing the candidate fingerprints (in every strictness mode), rather than silently picking the last-registered version.

Deserialization requirement: When using NAME, CLASS, or NUMERIC strategy, always provide a schema hint via CODEC_ROOT_SCHEMA or CODEC_ROOT_TYPE load option.

9.4 BSON Payload Size Limit

PropertyValue
ConstantCodecOptions.CODEC_MAX_PAYLOAD_SIZE
Default100 MB
ScopeBSON format provider
RecoveryIOException thrown before decoding

See the Security Analysis for details.

9.5 Reflection Allowlist for Type Conversion

PropertyValue
ConstantAttributeDeserializationEntry.SAFE_REFLECTION_TARGETS
Size13 types (+ 4 handled by direct code paths)
ScopeconvertObjectFromString() in attribute deserialization
RecoveryIllegalArgumentException → warning diagnostic, element skipped

When deserializing array attributes with custom component types (e.g., Date[], UUID[]), the codec uses reflection to invoke String constructors or valueOf/parse static methods. Only types on the SAFE_REFLECTION_TARGETS allowlist are permitted. All other types are rejected with a warning diagnostic. BigDecimal, BigInteger, UUID, and Date are handled by direct code paths before the allowlist check. The allowlist contains: URI, URL, and 11 java.time types (Instant, LocalDate, LocalTime, LocalDateTime, OffsetDateTime, ZonedDateTime, Duration, Period, Year, YearMonth, MonthDay).

9.6 Jackson StreamReadConstraints

PropertyValue
ConstantCodecResource.STREAM_READ_CONSTRAINTS
Max nesting depth500 (Jackson default: 1000) — backstop above codec's own 200 limit
Max string length10 MB (Jackson default: 20 MB)
Max field name length10 KB (Jackson default: 50 KB)
ScopeAll JSON/YAML/CBOR/BSON parsing via CodecResource
RecoveryJackson throws exception before codec processing

The codec configures Jackson's StreamReadConstraints with tighter limits than the 3.1.0 defaults. These limits are applied at the Jackson parser level, providing a first line of defense before the codec's own limits (nesting depth, collection size) are checked. The constraints are applied to:

  • Default JSON path (via CodecJsonFactory builder)
  • Format provider load path (doLoadWithFormat)
  • Format provider save path (doSaveWithFormat)

CWE reference: CWE-400 (Resource Exhaustion).


10. Option Reference Summary

Option KeyTypeDefaultDescription
codec.failFastBooleanfalseThrow on first error
codec.suppressWarningsBooleanfalseSuppress all warnings
codec.suppressWarningSourcesSet<String>emptySuppress warnings by source
codec.diagnosticHandlerDiagnosticHandlernullCustom handler
codec.deserializationModeDeserializationModeLENIENTStrictness level (see section 2.1)

Next: Annotation Reference →

Released under the EPL-2.0 License. Eclipse Fennec is part of the Eclipse Foundation.