BlogData ArchitectureEHR Data Integration Architecture: Epic, Cerner, and Oracle Health Compared
Data Architecture

EHR Data Integration Architecture: Epic, Cerner, and Oracle Health Compared

Epic, Cerner, and Oracle Health are the three dominant EHR systems — and each requires a different integration strategy. Here is what actually works for extracting clinical data into your warehouse.

mdatool Team·April 21, 2026·9 min read
EHR integrationEpicCernerOracle HealthFHIRHL7data pipeline

Introduction

Integrating with an EHR is never as clean as the vendor documentation suggests. Every implementation team has a story: the [FHIR](/terms/FHIR) endpoint that technically exists but times out for queries over 30 days, the HL7 feed that drops messages when census exceeds 80%, the bulk export that requires a ticket to the vendor's support team before it will actually execute. This guide documents what works in production — not what the integration specs say should work.

The three systems covered here — Epic, Cerner (now Oracle Health), and the Oracle Health platform (HealtheIntent / Millennium) — represent over 70% of US inpatient EHR market share. If you are building a clinical data pipeline, you will encounter at least one of them.


Integration Approaches Overview

Every EHR integration uses one or more of these mechanisms:

MethodLatencyCoverageReliability
FHIR R4 REST APINear-real-timeSelective resourcesModerate (rate limits, auth complexity)
FHIR Bulk Export ($export)HoursBroadHigh (designed for population export)
HL7 v2 ADT/ORU feedsNear-real-timeADT + lab resultsHigh (mature, well-understood)
Direct database readLow latencyFullHigh risk (unsupported, schema changes break pipelines)
Vendor ETL (Chronicles, Clarity, Millennium extract)Daily/weeklyBroadModerate (vendor-dependent)

The practical architecture for most organizations combines FHIR Bulk Export for historical population data with HL7 v2 real-time feeds for ADT and lab events.


Epic Integration

FHIR R4 API

Epic's FHIR R4 implementation (Interconnect) is SMART on FHIR-compliant and covers the US Core profile. For analytics use cases, the most relevant resources are:

  • Patient, Coverage, Encounter, Condition (ICD-10 diagnoses)
  • Observation (lab results, vital signs — LOINC-coded)
  • MedicationRequest, MedicationAdministration
  • Procedure (CPT-coded)
  • DocumentReference (clinical notes)

The gotcha: Epic's FHIR API is rate-limited. Typical limits are 500–1000 requests per minute per application. For population-level extraction, bulk export is required — direct FHIR API calls will hit rate limits and result in incomplete data.

Epic Bulk Data Export

Epic supports the FHIR Bulk Data Access specification ($export operation) through its Interconnect API. This is the correct mechanism for extracting full population data.

import requests
import time

# Initiate bulk export
response = requests.post(
    "https://[EPIC_FHIR_ENDPOINT]/api/FHIR/R4/$export",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/fhir+json",
        "Prefer": "respond-async"
    },
    json={
        "resourceType": "Parameters",
        "parameter": [
            {"name": "_type", "valueString": "Patient,Encounter,Condition,Observation"},
            {"name": "_since", "valueString": "2024-01-01T00:00:00Z"}
        ]
    }
)

# Epic returns 202 with Content-Location header for polling
polling_url = response.headers.get("Content-Location")

# Poll until complete (typically 15 min to 2 hours for large populations)
while True:
    status = requests.get(polling_url, headers={"Authorization": f"Bearer {access_token}"})
    if status.status_code == 200:
        export_manifest = status.json()
        break
    time.sleep(30)

The gotcha: Epic's bulk export can take 30 minutes to 4 hours depending on population size and server load. Build your pipeline to be asynchronous — do not hold a connection open.

Epic HL7 v2 Feeds

For real-time ADT and lab results, Epic sends HL7 v2 messages via MLLP over TCP/IP. These are mature, reliable, and the correct choice for real-time clinical event ingestion. Typical message types:

  • ADT^A01 — Patient Admit
  • ADT^A03 — Patient Discharge
  • ADT^A08 — Patient Information Update
  • ORU^R01 — Lab Result

Cerner (Oracle Health) Integration

FHIR R4 API

Cerner's FHIR implementation (Cerner Ignite) is also US Core-compliant. Coverage is similar to Epic, with strong support for:

  • Patient, Encounter, Condition, Observation, Procedure
  • CarePlan, Goal, AllergyIntolerance
  • DocumentReference (notes in DSTU2 and R4)

The gotcha: Cerner's FHIR API requires per-patient queries — there is no native population-level query without bulk export. For large health system integrations, this means your extraction logic must be patient-ID-driven.

Cerner Bulk Export

Cerner supports FHIR Bulk Export through its Health Data Intelligence platform. For organizations not on HDI, bulk extraction typically goes through Cerner's Millennium Extract (a direct HL7 or flat-file export from the Millennium database) or through the HealtheDataLab cloud offering.

Practical recommendation for Cerner: If your organization has an existing Millennium reporting database (Cerner's "Lights On" reporting environment or a custom Clarity-equivalent), use direct SQL extraction from the reporting replica rather than FHIR API for large-volume historical loads. FHIR API is better for incremental/delta loads.

Cerner CCL Scripts

Cerner Command Language (CCL) scripts can extract data directly from the Millennium database. This approach is faster than FHIR for bulk extraction but requires a Cerner-credentialed developer and carries schema-change risk:

-- CCL query to extract encounters for a date range
SELECT
  e.encntr_id,
  e.person_id,
  e.encntr_type_cd,
  cv.display AS encounter_type,
  e.reg_dt_tm,
  e.disch_dt_tm,
  p.name_full_formatted AS patient_name,
  p.birth_dt_tm AS date_of_birth
FROM encounter e
JOIN person p ON e.person_id = p.person_id
JOIN code_value cv ON e.encntr_type_cd = cv.code_value
WHERE e.reg_dt_tm >= CNVTDATETIME("01-JAN-2024 00:00:00")
  AND e.active_ind = 1

Oracle Health (HealtheIntent / Population Health Platform)

Oracle Health (formerly the Cerner population health platform, now integrated with Oracle's broader health platform) takes a different integration approach. Rather than direct EHR extraction, HealtheIntent ingests data from multiple sources (Cerner Millennium, third-party EHRs, claims) into a centralized data repository.

For organizations on HealtheIntent, the integration pattern is:

  1. Configure HealtheIntent to receive your source data feeds
  2. Use HealtheIntent's REST API or flat-file exports to extract processed clinical data
  3. Land processed data in your data warehouse

The advantage: HealtheIntent normalizes data from multiple source systems. If your health system operates multiple EHRs (Epic in one hospital, Cerner in another), HealtheIntent handles the normalization before your warehouse sees it.

The gotcha: HealtheIntent's API coverage is uneven. Some clinical domains (clinical quality measures, risk scores) are well-exposed. Others (raw encounter data, lab results) require file-based extraction.


Universal Integration Patterns

Regardless of EHR vendor, these patterns apply:

# Idempotent FHIR resource upsert (works for all three vendors)
def upsert_fhir_resource(resource: dict, warehouse_table: str):
    """
    Upsert a FHIR resource into the bronze layer.
    Uses FHIR resource ID as the deduplication key.
    """
    resource_id = resource.get("id")
    resource_type = resource.get("resourceType")
    last_updated = resource.get("meta", {}).get("lastUpdated")

    # Write to bronze table with FHIR resource ID as primary key
    # Downstream transformations handle normalization
    return {
        "fhir_resource_id": f"{resource_type}/{resource_id}",
        "resource_type": resource_type,
        "resource_json": json.dumps(resource),
        "last_updated": last_updated,
        "source_system": source_system,
        "ingested_at": datetime.utcnow().isoformat()
    }

Always validate HL7 messages before they enter your pipeline. Use the HL7 Parser to inspect message structure, verify segment completeness, and catch encoding issues before they propagate to your warehouse.


Key Takeaways

  • FHIR Bulk Export is the correct mechanism for population-level historical data. FHIR REST API is for incremental and patient-specific queries. Never use REST API for full-population extraction.
  • Epic and Cerner both support FHIR R4, but implementation details differ significantly. Test each endpoint independently — do not assume Epic behavior applies to Cerner.
  • HL7 v2 ADT feeds are the most reliable real-time mechanism for clinical events. Do not replace them with FHIR subscriptions until your FHIR infrastructure has proven reliability equal to your existing MLLP setup.
  • Cerner CCL direct extraction is faster than FHIR for bulk historical loads but requires credentialed Cerner access and carries schema-change risk.
  • Validate HL7 message structure at the ingestion boundary with the HL7 Parser before messages enter your pipeline.
M

mdatool Team

The mdatool team builds free engineering tools for healthcare data architects, analysts, and engineers working across payer, provider, and life sciences data.

Ready to improve your data architecture?

Free tools for DDL conversion, SQL analysis, naming standards, and more.

Get Started Free