---
title: ItemReader API
description: Complete reference for the ItemReader trait and all implementations
sidebar:
  order: 1
---

import { Aside, Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components';

The `ItemReader<I>` trait defines how to read items one at a time from any data source. It's the entry point of the chunk-oriented processing model.

## Trait Definition

```rust
pub trait ItemReader<I> {
    /// Reads the next item from the source
    ///
    /// # Returns
    /// - `Ok(Some(item))` - Successfully read an item
    /// - `Ok(None)` - End of data reached
    /// - `Err(BatchError)` - An error occurred
    fn read(&self) -> ItemReaderResult<I>;
}
```

<Aside type="tip">
  The `Option` in the return type signals end-of-data naturally without requiring a sentinel value.
</Aside>

## Type Alias

```rust
pub type ItemReaderResult<I> = Result<Option<I>, BatchError>;
```

## Available Implementations

Spring Batch RS provides 9 built-in reader implementations:

| Reader | Feature Flag | Data Source | Description |
|--------|-------------|-------------|-------------|
| `CsvItemReader<R>` | `csv` | CSV files/strings | Reads CSV records with configurable delimiters |
| `JsonItemReader<I, R>` | `json` | JSON arrays | Streaming JSON array parser |
| `XmlItemReader<R, I>` | `xml` | XML documents | Reads XML elements by tag name |
| `PostgresRdbcItemReader<I>` | `rdbc-postgres` | PostgreSQL | Paginated PostgreSQL queries |
| `MysqlRdbcItemReader<I>` | `rdbc-mysql` | MySQL/MariaDB | Paginated MySQL queries |
| `SqliteRdbcItemReader<I>` | `rdbc-sqlite` | SQLite | Paginated SQLite queries |
| `MongodbItemReader<I>` | `mongodb` | MongoDB | Cursor-based MongoDB queries |
| `OrmItemReader<I>` | `orm` | SeaORM | ORM-based database reading |
| `PersonReader` | `fake` | Fake data | Generates fake person data for testing |

---

## CSV Reader

<Aside type="note">
  **Feature flag:** `csv`
</Aside>

### Builder Methods

```rust
pub struct CsvItemReaderBuilder<R: Read> { /* ... */ }
```

| Method | Type | Default | Description |
|--------|------|---------|-------------|
| `has_headers(bool)` | `bool` | `true` | First row contains headers |
| `delimiter(u8)` | `u8` | `b','` | Field delimiter character |
| `quote(u8)` | `u8` | `b'"'` | Quote character |
| `flexible(bool)` | `bool` | `false` | Allow variable fields per row |
| `from_path(&str)` | - | - | Read from file path |
| `from_reader(R)` | - | - | Read from any `Read` source |

### Example

<Tabs>
  <TabItem label="From File">
    ```rust
    use spring_batch_rs::item::csv::CsvItemReaderBuilder;
    use serde::Deserialize;

    #[derive(Deserialize, Clone)]
    struct Product {
        id: u32,
        name: String,
        price: f64,
    }

    let reader = CsvItemReaderBuilder::<Product>::new()
        .has_headers(true)
        .delimiter(b',')
        .from_path("products.csv")?;
    ```
  </TabItem>
  <TabItem label="From String">
    ```rust
    let csv_data = "id,name,price\n1,Laptop,999.99\n2,Mouse,29.99";

    let reader = CsvItemReaderBuilder::<Product>::new()
        .has_headers(true)
        .from_reader(csv_data.as_bytes());
    ```
  </TabItem>
  <TabItem label="Custom Delimiter">
    ```rust
    let reader = CsvItemReaderBuilder::<Product>::new()
        .has_headers(true)
        .delimiter(b';')  // Semicolon-separated
        .from_path("data.csv")?;
    ```
  </TabItem>
</Tabs>

<Aside type="tip">
  Use `flexible(true)` for CSV files with inconsistent column counts.
</Aside>

---

## JSON Reader

<Aside type="note">
  **Feature flag:** `json`
</Aside>

### Builder Methods

```rust
pub struct JsonItemReaderBuilder<I, R: Read> { /* ... */ }
```

| Method | Type | Description |
|--------|------|-------------|
| `from_path(&str)` | - | Read from file path |
| `from_reader(R)` | - | Read from any `Read` source |

### Example

<Tabs>
  <TabItem label="From File">
    ```rust
    use spring_batch_rs::item::json::JsonItemReaderBuilder;
    use serde::Deserialize;

    #[derive(Deserialize, Clone)]
    struct User {
        id: u32,
        name: String,
        email: String,
    }

    let reader = JsonItemReaderBuilder::<User>::new()
        .from_path("users.json")?;
    ```
  </TabItem>
  <TabItem label="From String">
    ```rust
    let json_data = r#"[
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"}
    ]"#;

    let reader = JsonItemReaderBuilder::<User>::new()
        .from_reader(json_data.as_bytes());
    ```
  </TabItem>
</Tabs>

<Aside type="caution">
  The JSON reader expects a JSON array at the root level. Objects are read one at a time from the array.
</Aside>

---

## XML Reader

<Aside type="note">
  **Feature flag:** `xml`
</Aside>

### Builder Methods

```rust
pub struct XmlItemReaderBuilder<R: Read, I> { /* ... */ }
```

| Method | Type | Default | Description |
|--------|------|---------|-------------|
| `tag(&str)` | `&str` | **required** | XML tag name to extract |
| `capacity(usize)` | `usize` | `8192` | Buffer capacity in bytes |
| `from_path(&str)` | - | - | Read from file path |
| `from_reader(R)` | - | - | Read from any `Read` source |

### Example

```rust
use spring_batch_rs::item::xml::XmlItemReaderBuilder;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "vehicle")]
struct Vehicle {
    #[serde(rename = "@type")]
    vehicle_type: String,
    #[serde(rename = "@id")]
    id: String,
    make: String,
    model: String,
    year: i32,
}

let reader = XmlItemReaderBuilder::<Vehicle>::new()
    .tag("vehicle")  // Extract <vehicle> elements
    .capacity(1024)
    .from_path("vehicles.xml")?;
```

<Aside type="tip">
  Use `#[serde(rename = "@field")]` for XML attributes and `#[serde(rename = "$value")]` for text content.
</Aside>

---

## PostgreSQL Reader

<Aside type="note">
  **Feature flag:** `rdbc-postgres`
</Aside>

### Builder Methods

```rust
pub struct RdbcItemReaderBuilder<I> { /* ... */ }
```

| Method | Type | Default | Description |
|--------|------|---------|-------------|
| `postgres(PgPool)` | `PgPool` | **required** | PostgreSQL connection pool |
| `query(&str)` | `&str` | **required** | SQL SELECT query |
| `with_page_size(usize)` | `usize` | `100` | Number of rows per page |
| `build_postgres()` | - | - | Build PostgreSQL reader |

### Example

```rust
use spring_batch_rs::item::rdbc::RdbcItemReaderBuilder;
use sqlx::{PgPool, FromRow};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
struct Person {
    id: i64,
    first_name: String,
    last_name: String,
    email: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pool = PgPool::connect("postgres://user:pass@localhost/db").await?;

    let reader = RdbcItemReaderBuilder::<Person>::new()
        .postgres(pool)
        .query("SELECT id, first_name, last_name, email FROM persons WHERE active = true")
        .with_page_size(50)
        .build_postgres();

    Ok(())
}
```

<Aside type="tip">
  The reader automatically handles pagination using LIMIT/OFFSET internally.
</Aside>

---

## MySQL Reader

<Aside type="note">
  **Feature flag:** `rdbc-mysql`
</Aside>

### Builder Methods

Same as PostgreSQL reader, but use `mysql(MySqlPool)` and `build_mysql()`:

```rust
let reader = RdbcItemReaderBuilder::<Person>::new()
    .mysql(pool)
    .query("SELECT * FROM persons")
    .with_page_size(50)
    .build_mysql();
```

---

## SQLite Reader

<Aside type="note">
  **Feature flag:** `rdbc-sqlite`
</Aside>

### Builder Methods

Same as PostgreSQL reader, but use `sqlite(SqlitePool)` and `build_sqlite()`:

```rust
let reader = RdbcItemReaderBuilder::<Person>::new()
    .sqlite(pool)
    .query("SELECT * FROM persons")
    .with_page_size(50)
    .build_sqlite();
```

---

## MongoDB Reader

<Aside type="note">
  **Feature flag:** `mongodb`
</Aside>

### Builder Methods

```rust
pub struct MongodbItemReaderBuilder<I> { /* ... */ }
```

| Method | Type | Default | Description |
|--------|------|---------|-------------|
| `collection(&Collection<I>)` | `&Collection<I>` | **required** | MongoDB collection |
| `filter(Document)` | `Document` | `doc! {}` | Query filter |
| `page_size(u32)` | `u32` | `100` | Documents per batch |

### Example

```rust
use spring_batch_rs::item::mongodb::MongodbItemReaderBuilder;
use mongodb::{bson::doc, sync::Client};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
struct Book {
    title: String,
    author: String,
    isbn: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::with_uri_str("mongodb://localhost:27017")?;
    let db = client.database("library");
    let collection = db.collection::<Book>("books");

    let filter = doc! { "author": "J.K. Rowling" };

    let reader = MongodbItemReaderBuilder::new()
        .collection(&collection)
        .filter(filter)
        .page_size(20)
        .build();

    Ok(())
}
```

<Aside type="note">
  Your document type must implement `WithObjectId` trait if you want cursor-based pagination.
</Aside>

---

## ORM Reader

<Aside type="note">
  **Feature flag:** `orm`
</Aside>

Reads entities using SeaORM. Your entity must implement SeaORM's `EntityTrait`.

### Example

```rust
use spring_batch_rs::item::orm::OrmItemReaderBuilder;
use sea_orm::{Database, DatabaseConnection};

// Assuming you have a SeaORM entity
use entity::person::Entity as PersonEntity;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db: DatabaseConnection = Database::connect("sqlite::memory:").await?;

    let reader = OrmItemReaderBuilder::new()
        .entity(PersonEntity)
        .connection(&db)
        .page_size(50)
        .build();

    Ok(())
}
```

---

## Fake Data Reader

<Aside type="note">
  **Feature flag:** `fake`
</Aside>

Generates fake person data for testing purposes.

### Builder Methods

```rust
pub struct PersonReaderBuilder { /* ... */ }
```

| Method | Type | Default | Description |
|--------|------|---------|-------------|
| `number_of_items(usize)` | `usize` | **required** | How many persons to generate |

### Example

```rust
use spring_batch_rs::item::fake::person_reader::{PersonReaderBuilder, Person};

let reader = PersonReaderBuilder::new()
    .number_of_items(1000)
    .build();
```

The `Person` struct includes:
- `first_name: String`
- `last_name: String`
- `title: String`
- `email: String`
- `city: String`

<Aside type="tip">
  Perfect for testing your batch jobs without needing real data sources.
</Aside>

---

## Custom Implementation

You can implement `ItemReader` for any data source:

```rust
use spring_batch_rs::core::item::{ItemReader, ItemReaderResult};
use spring_batch_rs::error::BatchError;
use std::sync::Mutex;

struct MyCustomReader {
    data: Mutex<Vec<String>>,
}

impl ItemReader<String> for MyCustomReader {
    fn read(&self) -> ItemReaderResult<String> {
        let mut data = self.data.lock().unwrap();

        if data.is_empty() {
            Ok(None)  // End of data
        } else {
            Ok(Some(data.remove(0)))  // Return next item
        }
    }
}

impl MyCustomReader {
    fn new(items: Vec<String>) -> Self {
        Self {
            data: Mutex::new(items),
        }
    }
}
```

<Aside type="caution">
  **Thread Safety**: Readers must be `Send + Sync` since they may be accessed from multiple threads. Use `Mutex`, `RwLock`, or atomic types for internal state.
</Aside>

## Best Practices

<CardGrid>
  <Card title="Buffering" icon="rocket">
    Use buffered I/O for file-based readers to minimize system calls
  </Card>
  <Card title="Pagination" icon="list">
    Database readers should use pagination to avoid loading entire datasets into memory
  </Card>
  <Card title="Error Handling" icon="warning">
    Return descriptive `BatchError::ItemReader` errors with context about what failed
  </Card>
  <Card title="State Management" icon="setting">
    Use interior mutability (`Mutex`, `RefCell`) for stateful readers since `read()` takes `&self`
  </Card>
</CardGrid>

## See Also

- [ItemWriter API](/spring-batch-rs/api/item-writer/) - Writing data destinations
- [ItemProcessor API](/spring-batch-rs/api/item-processor/) - Transforming data
- [CSV Examples](/spring-batch-rs/examples/csv/) - Practical CSV examples
- [Database Examples](/spring-batch-rs/examples/database/) - Database reader examples
