skip to content

When would you use ListOutputConverter versus BeanOutputConverter, and what are ListOutputConverter's limits?

level: middleimportance: should knowfreq 45%

answer

  1. ListOutputConverter = flat List<String>, comma-separated
  2. uses DefaultConversionService to split
  3. BeanOutputConverter = JSON POJO / List<POJO>
  4. MapOutputConverter = Map<String,Object>
  5. ChatClient.entity picks the converter by type

basics

~20 s

Use 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 lines
java
import 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

for a junior

Knows ListOutputConverter gives a simple list of strings.

for a middle

Chooses correctly between List/Map/Bean converters and knows the comma-vs-JSON transport difference.

for a senior

Explains ConversionService splitting, comma-ambiguity pitfalls, and ParameterizedTypeReference for list-of-beans.

for a principal

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

context