> Markdown version of [AI-Generated Grids](https://vaadin.com/docs/next/flow/ai-support/ai-powered-grid). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# AI-Powered Grid (since V25.2)

`GridAIController` populates a [`Grid`](https://vaadin.com/docs/next/components/grid.md) (Vaadin’s component for displaying tabular data) with data from the application database based on natural-language requests. Backed by a [`DatabaseProvider`](https://vaadin.com/docs/next/flow/ai-support/controllers.md#database-provider), the controller lets the LLM inspect the database schema and run SQL queries that drive the grid.

> **Note: Commercial Feature**
>
> A commercial Vaadin subscription is required to use AI-Powered Grid in your project.
>
> - [Start Free Trial](https://vaadin.com/trial)
>
> - [See Pricing](https://vaadin.com/pricing)

When an LLM request completes, the queued query is executed and the grid re-renders with dynamically generated columns, type-appropriate renderers, right-aligned numeric columns, lazy loading via SQL `LIMIT`/`OFFSET`, and optional column grouping.

## <a id="basic-usage"></a>Basic Usage

Create a grid typed as `Grid<AIDataRow>`, construct a controller with the grid and a `DatabaseProvider`, and attach it to an orchestrator. `AIDataRow` is a framework-owned row type — you never construct instances yourself.

```java
Grid<AIDataRow> grid = new Grid<>();
MessageInput messageInput = new MessageInput();

DatabaseProvider databaseProvider = new JdbcDatabaseProvider(dataSource);
GridAIController controller = new GridAIController(grid, databaseProvider);

AIOrchestrator.builder(provider, systemPrompt)
        .withInput(messageInput)
        .withController(controller)
        .build();

add(messageInput, grid);
```

A message list is not required — the grid itself is the output surface. Example prompts:

- "Show product name and category grouped under Product, and monthly revenue."

- "List the top 10 highest-paid employees with name, salary, and hire date."

- "List all employees in the Sales department."

> **Tip: Built-In Workflow Instructions**
>
> The controller already informs the LLM of the workflow it needs. You can focus your own system prompt on application-specific behavior. Where the system prompt conflicts with a workflow step, the system prompt takes precedence.

## <a id="query-validation"></a>Query Validation

Before queueing an update, the controller runs a lightweight probe against the database to validate the LLM’s query. If the probe fails, the query is rejected and never reaches the grid.

The LLM can learn why the query failed (since V25.3), but only if your `DatabaseProvider` implementation opts in by throwing a `ToolException` from `executeQuery()` — its message is relayed to the LLM so it can correct the query on its next attempt. Any other exception is logged and replaced with a generic error message. See [Tool Error Handling](https://vaadin.com/docs/next/flow/ai-support/controllers.md#tool-error-handling).

## <a id="persisting-grid-state"></a>Persisting Grid State

Grid state is a single SQL query represented by the `GridState` record. Capture it with `getState()` and restore it later with `restoreState()`. Register a state change listener to persist the state automatically after each successful AI request:

```java
controller.addStateChangeListener(state ->
        sessionStore.save(sessionId, state));

// Restore on a new session
GridState saved = sessionStore.load(sessionId);
if (saved != null) {
    controller.restoreState(saved);
}
```

`sessionStore` here is a placeholder for your own storage — a database table, a file, a `VaadinSession` attribute, or whatever fits your application.

`addStateChangeListener()` fires only when the grid is updated by the LLM, not when `restoreState()` is called. The current state is also automatically included in session serialization, so no extra save/restore code is needed for in-session persistence.

## <a id="reconnecting-after-deserialization"></a>Reconnecting after Deserialization

`GridAIController` is not serializable. After session restore, create a new controller, pass it to `reconnect()` together with the new provider, and optionally re-apply the saved state:

```java
GridAIController controller = new GridAIController(grid, databaseProvider);
orchestrator.reconnect(provider)
        .withController(controller)
        .apply();
```
