Introduction
A2UI (Agent-to-User Interface) is a revolutionary protocol developed by Google that enables AI agents to dynamically render rich, interactive user interfaces. Unlike traditional REST APIs that return static JSON data, A2UI allows AI agents to generate complete UI experiences on-the-fly, creating a seamless bridge between intelligent backend services and modern frontend applications.
Complete demo is deployed here https://vishalmysore.github.io/simplea2ui/
You can use queries like
- Compare Honda and Toyota for me?
- What Food does Vishal like to eat?
- Can you book a restaruant for me? I need to eat food I am very hungry
What is A2UI Protocol?
A2UI v0.8 is a specification that defines how AI agents can communicate structured UI components to client applications. Instead of hardcoding UI logic in the frontend, agents can decide what interface to present based on context, user input, and application state.
Key Features
- Dynamic UI Generation: Agents create UI components programmatically based on business logic
- Component-Based Architecture: Uses standard UI components (Text, TextField, Button, Column, etc.)
- Data Model Binding: Two-way data binding between UI components and backend data models
- Action Handling: Buttons trigger backend actions with contextual data
- Protocol Flexibility: Supports both rich UI rendering and plain text responses
Technical Architecture
Server Implementation: A2AJava Framework
This project implements A2UI protocol using a2ajava (Actions for AI Java), a powerful framework that:
- Automatically exposes Java methods as AI-callable actions
- Provides annotation-driven agent and action definitions
- Integrates with Spring Boot for enterprise-grade applications
- Supports multiple callback types including A2UI, text, and custom protocols
Core Components
1. Agent Definition
Agents are defined using @Agent annotation with descriptive metadata:
@Service
@Agent(groupName = "compareCar", groupDescription = "compare 2 cars")
@Slf4j
public class CompareCarService implements A2UIDisplay {
private ThreadLocal<ActionCallback> callback = new ThreadLocal<>();
// ... implementation
}
2. Action Methods
Actions are Java methods annotated with @Action that can be invoked by AI systems:
@Action(description = "compare 2 cars")
public Object compareCar(String car1, String car2) {
log.info("Comparing cars: {} vs {}", car1, car2);
String betterCar = determineBetterCar(car1, car2);
// Dual-mode support: UI or text response
if(isUICallback(callback)) {
return createComparisonUI(car1, car2, betterCar, result);
} else {
return "Text-based response: " + betterCar + " is better";
}
}
3. Dual-Mode Protocol Support
The framework supports both text and UI protocols through intelligent callback detection:
- Text Mode: When called directly or via text-based AI systems, returns plain String responses
- UI Mode: When called with A2UI callback, returns structured component trees
// Detection logic
if(isUICallback(callback)) {
// Return A2UI component structure
return buildA2UIMessage(surfaceId, rootId, components);
} else {
// Return plain text
return "Simple text response";
}
A2UI Component Structure
Components Catalog
The protocol defines standard components:
- Text: Display static or dynamic text content
- TextField: Input fields with data model binding
- Button: Interactive buttons with action callbacks
- Column: Layout container for vertical arrangement
Data Model Format (A2UI v0.8 Spec)
Data models use adjacency list format for efficient state management:
{
"dataModelUpdate": {
"contents": [
{
"key": "form",
"valueMap": [
{
"key": "name",
"valueString": ""
},
{
"key": "email",
"valueString": ""
}
]
}
],
"surfaceId": "user_form"
}
}
TextField Component
TextFields bind to data model paths using the text property:
{
"component": {
"TextField": {
"label": { "literalString": "Person's Name" },
"text": { "path": "/form/name" }
}
},
"id": "name_input"
}
Button with Context
Buttons specify which data to send via context array:
{
"component": {
"Button": {
"action": {
"name": "whatThisPersonFavFood",
"context": [
{
"key": "name",
"value": { "path": "/form/name" }
}
]
},
"child": "submit_button_text"
}
},
"id": "submit_button"
}
Implementation Examples
Simple Service with Form Submission
@Service
@Agent(groupName = "whatThisPersonFavFood",
groupDescription = "Provide persons name and find what they like")
public class SimpleService implements A2UIDisplay {
@Action(description = "Get the favourite food of a person")
public Object whatThisPersonFavFood(String name) {
String favFood = lookupFavoriteFood(name);
if(callback != null && callback.getType().equals(CallBackType.A2UI.name())) {
return createFavoriteFoodUI(name, favFood);
} else {
return favFood;
}
}
private Map<String, Object> createFavoriteFoodUI(String name, String favFood) {
// Create UI components
List<Map<String, Object>> components = new ArrayList<>();
// Add title
components.add(createTextComponent("title", "Favorite Food Finder", "h2"));
// Add result display
components.add(createTextComponent("result",
name + "'s favorite food is: " + favFood));
// Add form for next query
components.add(createTextFieldComponent("name_input",
"Person's Name", "/form/name"));
// Add submit button with context binding
Map<String, String> contextBindings = new HashMap<>();
contextBindings.put("name", "/form/name");
components.add(createButtonComponent("submit_button",
"Find Favorite Food", "whatThisPersonFavFood", contextBindings));
// Initialize data model
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("/form/name", "");
return buildA2UIMessageWithData(surfaceId, rootId, components, dataModel);
}
}
Multi-Step Workflow: Restaurant Booking
Complex workflows with multiple action methods:
@Service
@Agent(groupName = "restaurantBooking",
groupDescription = "Book restaurant reservations with menu selection")
public class RestaurantBookingService implements A2UIDisplay {
@Action(description = "Book a restaurant reservation - shows form")
public Object bookRestaurantReservation(String restaurantName) {
if(isUICallback(callbackThreadLocal)) {
return createReservationFormUI(restaurantName);
}
return "Please provide reservation details...";
}
@Action(description = "Confirm restaurant reservation with all details")
public Object confirmReservation(String restaurantName, String date,
String time, int numberOfPeople,
String menuType, String specialRequests) {
String confirmationNumber = generateConfirmationNumber();
if(isUICallback(callbackThreadLocal)) {
return createConfirmationUI(restaurantName, date, time,
numberOfPeople, menuType,
specialRequests, confirmationNumber);
}
return "Reservation confirmed! Confirmation #" + confirmationNumber;
}
}
Technical Benefits
1. Separation of Concerns
- Backend controls UI logic and workflow
- Frontend focuses on rendering and user interaction
- Clean API boundaries with strong typing
2. Dynamic Adaptation
- UI adapts to business rules without frontend redeployment
- A/B testing at the protocol level
- Personalized experiences based on user context
3. Type Safety
- Java type system ensures correct data structures
- Compile-time validation of component hierarchies
- IDE support for autocomplete and refactoring
4. Backward Compatibility
- Same codebase supports legacy text-based clients
- Graceful degradation for unsupported clients
- Progressive enhancement for modern interfaces
A2UIDisplay Utility Interface
The project provides a utility interface with helper methods:
public interface A2UIDisplay {
// Create text components
Map<String, Object> createTextComponent(String id, String text, String usageHint);
// Create input fields with data binding
Map<String, Object> createTextFieldComponent(String id, String label, String dataPath);
// Create buttons with action callbacks and context
Map<String, Object> createButtonComponent(String id, String buttonText,
String actionName,
Map<String, String> contextBindings);
// Create layout containers
Map<String, Object> createRootColumn(String rootId, List<String> childIds);
// Build complete A2UI messages
Map<String, Object> buildA2UIMessageWithData(String surfaceId, String rootId,
List<Map<String, Object>> components,
Map<String, Object> dataModelValues);
}
Data Flow Architecture
Request-Response Cycle
- AI Request: AI system calls action method with parameters
- Callback Detection: Service checks callback type (A2UI vs text)
- Business Logic: Execute core functionality
-
Response Generation:
- A2UI: Build component tree with data model
- Text: Return simple string
- Client Rendering: Frontend processes A2UI message and renders UI
- User Interaction: User fills form and clicks button
- Context Submission: Button action sends bound data back to backend
- Next Action: Cycle repeats with new action method
Data Model Lifecycle
1. Backend initializes data model with empty values
└─> dataModelUpdate.contents = [{ key: "form", valueMap: [...] }]
2. Frontend binds TextField.text to data model paths
└─> { "text": { "path": "/form/name" } }
3. User types input → Frontend updates data model in real-time
4. User clicks button → Frontend extracts context values
└─> Button.action.context specifies which paths to send
5. Backend receives action call with populated parameters
└─> confirmReservation(restaurantName="Joe's Diner", date="2026-01-15", ...)
Best Practices
1. Consistent Naming
- Use descriptive action names matching button actions
- Data model paths follow REST-like conventions:
/resource/field
2. Error Handling
- Validate input in action methods
- Return user-friendly error messages in UI mode
3. State Management
- Use ThreadLocal for callback storage in multi-threaded environments
- Initialize data models with sensible defaults
4. Component Hierarchy
- Keep component trees shallow for better performance
- Use semantic component IDs for debugging
5. Context Binding
- Only bind necessary data to button context
- Use explicit parameter names matching action method signatures
Integration with Spring Boot
The framework seamlessly integrates with Spring Boot:
@Service // Spring managed bean
@Agent(groupName = "myAgent", groupDescription = "Agent description")
public class MyService implements A2UIDisplay {
@Autowired
private SomeDependency dependency; // Spring dependency injection works
private ActionCallback callback; // Autowired by tools4ai framework
private AIProcessor processor; // Autowired by tools4ai framework
@Action(description = "Action description")
public Object myAction(String param) {
// Use injected dependencies
// Access callback and processor
}
}
Conclusion
A2UI protocol represents a paradigm shift in how AI agents interact with users. By combining the power of intelligent backend services with dynamic UI generation, developers can build highly adaptive applications that respond to user needs in real-time.
The a2ajava framework makes implementing A2UI straightforward through:
- Annotation-driven development
- Dual-mode protocol support (text and UI)
- Spring Boot integration
- Type-safe component generation
- Reusable utility interfaces
This architecture enables teams to build sophisticated conversational UIs, multi-step workflows, and context-aware applications while maintaining clean separation between business logic and presentation layers.
Resources
- A2UI Specification: https://github.com/google/A2UI
- a2ajava Framework: Enterprise Java implementation for AI actions
- Spring Boot: https://spring.io/projects/spring-boot
- Project Repository: Complete working examples and documentation
Article written for springa2ui project - A demonstration of A2UI protocol implementation using a2ajava framework and Spring Boot

Top comments (0)