# Spring AI Structured Output

> A complete guide for handling structured outputs in Spring AI, covering JSON Schema mode, type-safe deserialization, and validation.

- Skill: `mat-garcia/spring-ai-structured-output` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mat-garcia/spring-ai-structured-output`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mat-garcia/spring-ai-structured-output/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: Complete terms in LICENSE.txt
- Author: mat-garcia (https://skillmd.com/u/mat-garcia)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mat-garcia/spring-ai-structured-output

---

# Spring AI - Structured Output & Type-Safe Responses

## Description

Complete guide for handling structured outputs in Spring AI. Covers JSON Schema mode, type-safe entity deserialization, supported output formats, and integration with Spring data validation.

## When to Use

- Type-safe LLM responses
- JSON Schema validation mode
- Structured data extraction
- API response parsing
- Form filling
- Data classification
- Batch processing
- Integration with Spring data entities

## Topics Covered

### 1. Structured Output Modes

- **JSON Schema Mode**: Latest capability, enforced format
- **Parser-based**: Client-side parsing
- **Converter-based**: Structured conversion
- **Type-safe mapping**: Java entity mapping

### 2. Output Formats

- **JSON**: Primary structured format
- **YAML**: Alternative format
- **XML**: Some providers support
- **CSV**: Tabular data
- **Protobuf**: Binary structured format
- **Custom formats**: Provider-specific

### 3. Converter Types

#### StructuredOutputConverter

- Generic converter for any type
- JSON Schema generation
- Validation against schema
- Error recovery

#### BeanOutputConverter (Deprecated)

- Legacy entity conversion
- Still works but not recommended
- Use StructuredOutputConverter instead

#### Jackson/Gson Integration

- Direct JSON binding
- Annotation support
- Custom serialization
- Type polymorphism

### 4. Type Support

- **Simple types**: String, int, boolean, etc.
- **Collections**: List, Set, Map, arrays
- **Nested objects**: Complex hierarchies
- **Generic types**: Parameterized types
- **Enums**: Enumeration values
- **Records**: Java 14+ records

### 5. Validation

- **Schema validation**: JSON Schema compliance
- **Bean validation**: JSR-303 annotations
- **Custom validators**: Business logic
- **Error handling**: Invalid response handling
- **Fallback strategies**: Partial results

## Code Patterns

### Basic Structured Output

```java
@Service
public class StructuredService {
    @Autowired
    private ChatClient chatClient;

    public Person generatePerson() {
        return chatClient.prompt()
            .user("Generate a person with name and age")
            .call()
            .entity(Person.class);
    }
}

@Data
class Person {
    String name;
    int age;
    String email;
}
```

### Type-Safe Response with Nested Objects

```java
record Article(
    String title,
    String content,
    Author author,
    List<String> tags,
    LocalDateTime publishedAt
) {}

record Author(
    String name,
    String email
) {}

@Service
public class ArticleService {
    @Autowired
    private ChatClient chatClient;

    public Article generateArticle(String topic) {
        return chatClient.prompt()
            .user("Write an article about " + topic)
            .call()
            .entity(Article.class);
    }
}
```

### Structured Output with Validation

```java
@Data
class Product {
    @NotBlank
    String name;

    @Min(0)
    BigDecimal price;

    @NotEmpty
    List<String> categories;

    @Pattern(regexp = "^[A-Z0-9]{5,}$")
    String sku;
}

@Service
public class ProductService {
    @Autowired
    private ChatClient chatClient;

    @Autowired
    private Validator validator;

    public Product extractProduct(String description) {
        Product product = chatClient.prompt()
            .user("Extract product info: " + description)
            .call()
            .entity(Product.class);

        // Validate
        Set<ConstraintViolation<Product>> violations =
            validator.validate(product);

        if (!violations.isEmpty()) {
            throw new ValidationException("Invalid product: " + violations);
        }

        return product;
    }
}
```

### Collection Output

```java
record SearchResults(
    List<Result> items,
    int totalCount,
    String nextPageToken
) {}

record Result(
    String id,
    String title,
    float relevanceScore
) {}

@Service
public class SearchService {
    @Autowired
    private ChatClient chatClient;

    public SearchResults structuredSearch(String query) {
        return chatClient.prompt()
            .user("Search and rank results for: " + query)
            .call()
            .entity(SearchResults.class);
    }
}
```

### Enum Classification

```java
enum SentimentScore {
    VERY_NEGATIVE, NEGATIVE, NEUTRAL, POSITIVE, VERY_POSITIVE
}

record SentimentAnalysis(
    String text,
    SentimentScore sentiment,
    float confidence,
    String explanation
) {}

@Service
public class SentimentService {
    @Autowired
    private ChatClient chatClient;

    public SentimentAnalysis analyzeSentiment(String text) {
        return chatClient.prompt()
            .user("Analyze sentiment of: " + text)
            .call()
            .entity(SentimentAnalysis.class);
    }
}
```

### Batch Structured Processing

```java
@Service
public class BatchStructuredService {
    @Autowired
    private ChatClient chatClient;

    public List<Classification> classifyBatch(List<String> texts) {
        return texts.stream()
            .map(text -> chatClient.prompt()
                .user("Classify: " + text)
                .call()
                .entity(Classification.class))
            .toList();
    }
}

record Classification(
    String text,
    String category,
    float confidence
) {}
```

### Generic Structured Output

```java
@Service
public class GenericStructuredService {
    @Autowired
    private ChatClient chatClient;

    public <T> T extractStructured(String prompt, Class<T> type) {
        return chatClient.prompt()
            .user(prompt)
            .call()
            .entity(type);
    }

    // Usage:
    List<String> categories = extractStructured(
        "Generate 5 categories",
        List.class  // Returns List<String>
    );
}
```

### Error Handling for Structured Output

```java
@Service
public class RobustStructuredService {
    @Autowired
    private ChatClient chatClient;

    public Person extractPersonSafely(String text) {
        try {
            return chatClient.prompt()
                .user("Extract person: " + text)
                .call()
                .entity(Person.class);
        } catch (StructuredOutputException e) {
            logger.warn("Failed to parse structured output", e);

            // Fallback: retry with different prompt
            return chatClient.prompt()
                .user("Parse carefully: " + text)
                .call()
                .entity(Person.class);
        } catch (ValidationException e) {
            logger.error("Invalid structured data", e);
            return null;  // or return default
        }
    }
}
```

### Custom Output Converter

```java
public class CustomOutputConverter<T> implements ResponseConverter<T> {
    private final Class<T> type;
    private final ObjectMapper mapper;

    public CustomOutputConverter(Class<T> type) {
        this.type = type;
        this.mapper = new ObjectMapper();
    }

    @Override
    public T convert(String response) {
        try {
            // Parse JSON
            JsonNode root = mapper.readTree(response);

            // Custom validation
            validateSchema(root);

            // Map to type
            return mapper.treeToValue(root, type);
        } catch (Exception e) {
            throw new ResponseConversionException(
                "Failed to convert: " + e.getMessage(), e);
        }
    }

    private void validateSchema(JsonNode root) {
        // Custom schema validation logic
    }
}
```

### Spring Data Entity Output

```java
@Entity
@Data
public class BlogPost {
    @Id
    Long id;

    @NotBlank
    String title;

    @Column(columnDefinition = "TEXT")
    String content;

    @ElementCollection
    List<String> tags;

    @CreationTimestamp
    LocalDateTime createdAt;
}

@Service
public class BlogService {
    @Autowired
    private ChatClient chatClient;

    public BlogPost generateBlogPost(String topic) {
        BlogPost post = chatClient.prompt()
            .user("Write blog post about: " + topic)
            .call()
            .entity(BlogPost.class);

        return blogRepository.save(post);
    }
}
```

## Configuration

### Enable Structured Output

```java
@Configuration
public class StructuredOutputConfig {
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultOptions(ChatOptions.builder()
                .withResponseFormat("json_object")
                .build())
            .build();
    }
}
```

### Custom ObjectMapper

```java
@Configuration
public class ObjectMapperConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
}
```

### Properties

```properties
spring.ai.chat.options.response-format=json_object
spring.ai.structured-output.validation=true
spring.ai.structured-output.strict-schema=true
```

## Best Practices

- Use records for immutable structured data
- Always validate structured outputs
- Implement custom validators for complex logic
- Use generic types for reusable converters
- Document schema expectations
- Implement fallback strategies
- Monitor structured output success rates
- Cache schemas when possible

## Related Skills

- `chat-models/SKILL.md` - Chat operations
- `tools-agents/SKILL.md` - Tool return types
- `error-handling/SKILL.md` - Error recovery

## References

- API: `/pages/api/structured-output-converter.adoc`
- JSON Schema: OpenAI function calling docs
- Validation: Jakarta Bean Validation docs

