When would you use ListOutputConverter versus BeanOutputConverter, and what are ListOutputConverter's limits?
answer
- ListOutputConverter = flat List<String>, comma-separated
- uses DefaultConversionService to split
- BeanOutputConverter = JSON POJO / List<POJO>
- MapOutputConverter = Map<String,Object>
- ChatClient.entity picks the converter by type
basics
~20 sUse ListOutputConverter for a simple List<String> — it tells the model to reply as a comma-separated list and splits it. Use BeanOutputConverter for structured objects (POJOs) or lists of objects, which come back as JSON.
solid answer
~40 s`ListOutputConverter` targets the narrow case of a flat `List<String>`: its `getFormat()` instructs the model to return a comma-delimited list (e.g. `foo, bar, baz`), and `convert()` uses a Spring `ConversionService` (`DefaultConversionService`) to split that into a `List<String>`. It cannot produce typed objects or nested structures. For anything richer — a POJO, a record, or a `List<SomePojo>` — you use `BeanOutputConverter`, which drives JSON-schema-based output and Jackson deserialization. So: single dimension of strings → ListOutputConverter; structured data or lists of structured data → BeanOutputConverter (with a `ParameterizedTypeReference` for the generic list). Both are `StructuredOutputConverter`s and both are reachable through `ChatClient.entity(...)`, which picks the right converter based on the type you pass.
code
java · 21 linesimport org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.core.convert.support.DefaultConversionService;
import java.util.List;
import java.util.Map;
public class TagLister {
private final ChatModel chatModel;
TagLister(ChatModel chatModel) { this.chatModel = chatModel; }
List<String> tagsFor(String topic) {
ListOutputConverter converter =
new ListOutputConverter(new DefaultConversionService());
String reply = chatModel.call(new PromptTemplate(
"List 5 keywords for {topic}.\n{format}")
.create(Map.of("topic", topic, "format", converter.getFormat())))
.getResult().getOutput().getText();
return converter.convert(reply); // comma-separated -> List<String>
}
}go deeper
Knows ListOutputConverter gives a simple list of strings.
Chooses correctly between List/Map/Bean converters and knows the comma-vs-JSON transport difference.
Explains ConversionService splitting, comma-ambiguity pitfalls, and ParameterizedTypeReference for list-of-beans.
Advises on format-robustness tradeoffs (delimited vs JSON) and when native structured output beats converter prompting for collections.
**The converter family.** All three built-ins implement `StructuredOutputConverter<T>` (`FormatProvider` + `Converter<String,T>`): - `ListOutputConverter` → `List<String>` - `MapOutputConverter` → `Map<String,Object>` - `BeanOutputConverter<T>` → arbitrary POJO / generic type **ListOutputConverter mechanics.** It is constructed with a `ConversionService` (commonly `new ListOutputConverter(new DefaultConversionService())`). Its `getFormat()` produces an instruction like *"Respond with a comma-separated list of values… e.g. `foo, bar, baz`"*. Its `convert(String)` then relies on the conversion service's String→List split. Because the transport is a comma-delimited line, it is inherently limited to a **flat list of strings** — no nesting, no typed elements, and values containing commas are ambiguous. **Why not ListOutputConverter for objects.** If you need `List<Author>` where `Author` has fields, a comma-separated line cannot encode structure. You must use JSON, which is what `BeanOutputConverter` produces. For the list-of-beans case, preserve the element type with a `ParameterizedTypeReference<List<Author>>` — a raw `Class` erases the generic and Jackson can't reconstruct elements. **MapOutputConverter.** Between the two: returns a `Map<String,Object>` (JSON object) when you want keyed data but don't have (or want) a POJO class. **ChatClient sugar.** `chatClient.prompt().user(q).call().entity(...)` inspects the type argument and selects the converter: pass `new ParameterizedTypeReference<List<String>>(){}` → list behavior; pass a bean `Class`/reference → bean behavior. You rarely instantiate converters by hand when using ChatClient. **Gotchas.** - ListOutputConverter output with embedded commas or delimiters in a value gets mis-split — prefer BeanOutputConverter+JSON when values are free text. - `List<String>` via ListOutputConverter is comma-delimited; `List<Pojo>` via BeanOutputConverter is JSON — different wire formats, don't conflate. - As with all these converters, adherence isn't guaranteed; malformed output can fail conversion. **When to use.** - Enumerations / tags / keyword lists of plain strings → `ListOutputConverter`. - Any structured record, or a collection of structured records → `BeanOutputConverter` (+ ParameterizedTypeReference for lists). - Loose keyed data without a class → `MapOutputConverter`.
- What wire format does each converter expect from the model?ListOutputConverter expects a comma-delimited line of strings; BeanOutputConverter and MapOutputConverter expect JSON (an object, or an array for a list of beans).
- Why can't ListOutputConverter give you List<Author>?Its transport is a flat comma-separated string with no way to encode an object's fields or nesting. Structured elements require JSON, so you use BeanOutputConverter with ParameterizedTypeReference<List<Author>>.
saying these in an interview costs you the question
- Using ListOutputConverter to try to get a list of objects
- Thinking ListOutputConverter returns JSON
- Not knowing it splits on commas via a ConversionService
- Assuming a raw Class preserves List<T> generics for bean conversion