Java EDI Parser User Guide

Overview

The EDI Parser for Java reads X12 EDI and maps it to regular Java business objects. Instead of navigating loops, segments, elements, and qualifiers directly, your application can work with objects such as Claim, Payment, MemberCoverage, ClaimStatus, ServiceLine, and OrgOrPerson.

The general parsing pattern is the same for every supported transaction:

  1. Create an EdiParser for a File or Reader.
  2. Optionally enable validation.
  3. Parse the input in chunks.
  4. Iterate over the root objects returned for each chunk.
  5. Process each root object according to its Java type.
  6. Continue until EdiParsingResults.isDone() returns true.
  7. Close the parser, preferably with try-with-resources.

Before using the examples, add the parser library to your project and configure a license key.

Parsing Flow

Use parse(int chunkSize) for files that may contain many transactions or business objects. Chunked parsing limits the amount of parsed data held in memory at one time and lets your application process each batch before reading more input.

try (var parser = new EdiParser(ediFile)
        .isValidationMode(true)) {

    EdiParsingResults results;
    do {
        results = parser.parse(20);

        for (var rootObj : results.rootObjs()) {
            if (rootObj instanceof Claim claim) {
                processClaim(claim);
            }
            else if (rootObj instanceof EdiTransaction transaction) {
                processTransaction(transaction);
            }
            else if (rootObj instanceof InterchangeControl interchange) {
                processInterchange(interchange);
            }
            else if (rootObj instanceof FunctionalGroup group) {
                processFunctionalGroup(group);
            }
            else if (rootObj instanceof ValidationIssue issue) {
                processValidationIssue(issue);
            }
            else {
                throw new IllegalStateException(
                        "Unexpected root object: " + rootObj.getClass().getName());
            }
        }
    } while (!results.isDone());
}

rootObjs() preserves the order in which root objects appear in the EDI file. This is useful when a file contains multiple interchanges, functional groups, transactions, or transaction-specific object types.

For a small file, parse() reads all available input in one call. You can then use rootObjs() or a typed collection such as claims():

try (var parser = new EdiParser(ediFile)) {
    EdiParsingResults results = parser.parse();
    for (Claim claim : results.claims()) {
        processClaim(claim);
    }
}

Prefer parse() and parse(int) for new code. The older transaction-specific methods parse837(int) and parse835(int) are deprecated.

Root Objects

Every chunk can contain shared envelope and transaction objects:

  • InterchangeControl represents the ISA/IEA interchange envelope.
  • FunctionalGroup represents the GS/GE functional group.
  • EdiTransaction contains transaction-level data from the ST/SE transaction.
  • ValidationIssue represents an issue associated with the transaction or EDI structure.

The transaction-specific business root objects depend on the EDI transaction:

EDI transaction Business root objects
837P, 837I, and 837D Claim
835 Payment and ProviderAdjustment
834 MemberCoverage
277CA ClaimStatus, ProviderStatus, and ReceiverStatus

EdiParsingResults also provides typed collections when file order is not important:

  • claims()
  • payments()
  • providerAdjustments()
  • memberCoverages()
  • claimStatuses()
  • ediTransactions()

Use rootObjs() when you need all object types in source order. Use the typed collections when you only need one kind of business object from the current chunk.

Object Model

The parser returns plain Java objects such as Claim, Payment, ServiceLine, PatientSubscriber, OrgOrPerson, and CodeEntity. These objects abstract X12 loops and segment layouts while retaining the data mapped from the source EDI.

For example, a Claim is the root of the object graph for an 837 claim. It combines data from the CLM loop, including references, dates, providers, subscriber and patient information, diagnoses, and service lines. Each ServiceLine contains line-level fields such as procedure or revenue codes, dates, amounts, quantities, providers, and adjustments.

The same object model is used by the Java EDI generation API, so parsed objects can be modified and written back to EDI.

See the EDI schema documentation for complete field definitions and X12 mappings:

Parse 837 Claims

Claim is the business root object for professional, institutional, and dental claims. The same parsing flow works for each 837 transaction type.

private void processClaim(Claim claim) {
    String patientControlNumber = claim.patientControlNumber();
    BigDecimal chargeAmount = claim.chargeAmount();

    OrgOrPerson billingProvider = claim.billingProvider();
    String billingProviderNpi = billingProvider.identifier();

    PatientSubscriber subscriber = claim.subscriber();
    String subscriberId = subscriber.person().identifier();
    String payerId = subscriber.payer().identifier();

    for (var diagnosis : claim.diags()) {
        String diagnosisCode = diagnosis.code();
    }

    for (ServiceLine line : claim.lines()) {
        String procedureCode = line.procedure() == null
                ? null
                : line.procedure().code();
        String revenueCode = line.revenueCode() == null
                ? null
                : line.revenueCode().code();
        LocalDate serviceDate = line.serviceDateFrom();
        BigDecimal lineChargeAmount = line.chargeAmount();
    }
}

Professional lines normally use procedure codes. Institutional lines can use revenue codes, and their procedure code may be absent. Institutional claims can also contain claim-level procedures, occurrence codes, value codes, occurrence spans, and present-on-admission indicators.

The complete example parses both 837P and 837I, processes transaction metadata, providers, subscribers, patients, diagnoses, institutional codes, service lines, and validation issues.

View the complete 837 parsing example in GitHub.

Parse 835 Payments

An 835 can produce two business root types:

  • Payment represents one adjudicated claim from a CLP loop.
  • ProviderAdjustment represents a provider-level PLB adjustment that is not associated with one specific claim.
if (rootObj instanceof Payment payment) {
    String patientControlNumber = payment.patientControlNumber();
    BigDecimal billedAmount = payment.chargeAmount();
    BigDecimal paidAmount = payment.paymentAmount();
    AdjudicatedClaimStatus status = payment.claimStatus();

    for (var adjustment : payment.adjustments()) {
        String reasonCode = adjustment.reasonCode();
        BigDecimal amount = adjustment.amount();
    }

    for (ServiceLine line : payment.lines()) {
        BigDecimal lineBilledAmount = line.chargeAmount();
        BigDecimal linePaidAmount = line.paidAmount();
    }
}
else if (rootObj instanceof ProviderAdjustment adjustment) {
    String providerId = adjustment.providerIdentifier();
    LocalDate fiscalPeriodDate = adjustment.fiscalPeriodDate();
}

Payment transactions also contain payment-level fields such as the payment method, payment date, check or EFT trace number, and total payment amount.

View the complete 835 parsing example in GitHub.

Parse 834 Member Coverage

MemberCoverage represents an INS member detail loop and its related segments. It contains the sponsor, insurer, member, policy or group information, and a list of HealthCoverage objects from the 2300 loops.

if (rootObj instanceof MemberCoverage memberCoverage) {
    String sponsorId = memberCoverage.sponsor().identifier();
    String insurerId = memberCoverage.insurer().identifier();
    String memberId = memberCoverage.identifier();
    String groupNumber = memberCoverage.groupOrPolicyNumber();

    Member member = memberCoverage.member();
    for (HealthCoverage coverage : memberCoverage.healthCoverages()) {
        processHealthCoverage(coverage);
    }
}

View the complete 834 parsing example in GitHub.

Parse 277CA Claim Acknowledgments

A 277CA can report acknowledgment status at several levels:

  • ClaimStatus contains a claim’s patient control number, billing provider, patient, claim status information, and any service-line statuses.
  • ProviderStatus reports status for a provider rather than an individual claim.
  • ReceiverStatus reports a transaction-level rejection when no claim-level statuses are available.

Each status can contain multiple StatusInfo objects, and each StatusInfo can contain multiple category and status code pairs.

if (rootObj instanceof ClaimStatus claimStatus) {
    String patientControlNumber = claimStatus.patientControlNumber();

    for (StatusInfo status : claimStatus.statusInfos()) {
        StatusActionType action = status.actionType();
        for (StatusCodeInfo code : status.statusCodeInfos()) {
            String categoryCode = code.categoryCode();
            String statusCode = code.statusCode();
        }
    }

    for (ServiceLineStatus lineStatus : claimStatus.serviceLineStatuses()) {
        String lineControlNumber = lineStatus.controlNumber();
    }
}

The complete example covers normal claim acknowledgments, service-line rejections, provider-level statuses, and receiver-level transaction rejections.

View the complete 277CA parsing example in GitHub.

Validation

Enable validation when creating the parser:

try (var parser = new EdiParser(ediFile)
        .isValidationMode(true)) {
    EdiParsingResults results = parser.parse();
    for (ValidationIssue issue : results.allValidationIssues()) {
        System.out.println(issue.toFullMessage());
    }
}

Validation issues can be associated with different levels of the parsed data:

  • results.validationIssues() contains transaction-level issues for the current chunk.
  • Transaction-level ValidationIssue objects also appear in rootObjs() in source order.
  • Business roots such as Claim, Payment, and MemberCoverage expose their own validationIssues() collections.
  • results.allValidationIssues() combines transaction-level and object-level validation issues for the current chunk.

The parser logs validation issues automatically. Use these collections when your application needs to store, display, filter, or reject data based on validation results.

Parsing failures and malformed EDI details are also available from results.parsingIssues().

Enums and EDI Qualifiers

The parser translates many EDI qualifiers and codes into Java enums. For example:

Claim claim = results.claims().get(0);

PlaceOfServiceType placeOfService = claim.placeOfServiceType();
PatientSubscriber subscriber = claim.subscriber();
EntityRole role = subscriber.person().entityRole();
EntityType entityType = subscriber.person().entityType();
GenderType gender = subscriber.person().gender();

// Convert an enum back to its EDI qualifier or code.
String ediValue = role.ediValue();

View the complete enum parsing example in GitHub.

Segment-Level Parsing

Use the segment API when you need the raw segment tree, want to find elements by segment and position, or are working with a transaction that does not have a business object model.

The segment example shows how to find segments, navigate child loops, read elements by position or name, format segment trees, and convert segments to Jackson JsonNode objects.

View the complete segment parsing example in GitHub.

Run the Examples

The complete examples are JUnit tests in the hdi.edi.parser package. After configuring your license, clone the repository and run the suite from the java-parser directory:

./gradlew test

The examples read EDI files from the repository’s top-level edi_files directory. The shared paths and chunk size are defined in ParsingExampleHelper.java.

You can verify the installed parser version and license information with VersionAndLicenseInfoExample.java.