Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

LangChain4j Structured Output

Return typed objects from LangChain4j AI Services: POJO and record return types, enums, lists, JSON schema mode and validation — no manual parsing of model responses.

Intermediate4 min readUpdated
On this page

Structured output is where LangChain4j's declarative style pays off most. You declare a return type on an interface method, and the library derives the schema, constrains the model and deserialises the response. This is how you wire a model into typed application code instead of parsing text by hand.

Key Takeaways

  • The return type drives everything — records, enums, lists, booleans all work.
  • Enums constrain the model to your permitted values; prefer them over strings.
  • With a supporting provider, JSON schema mode guarantees structural validity.
  • Structured output guarantees shape, not correctness — validate the values yourself.

Typed return values

The return type of the interface method is the contract:

Types that just work
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
 
record Ticket(String summary, Priority priority, List<String> components) {
    enum Priority { LOW, MEDIUM, HIGH, CRITICAL }
}
 
interface Analyst {
    // Enum — constrained to three values.
    Sentiment sentimentOf(String review);
 
    // Record — schema derived from the fields.
    Ticket triage(String bugReport);
 
    // Boolean — trivial routing logic.
    boolean isSpam(String message);
 
    // List of records.
    List<Ticket> triageAll(String bugReports);
}
Analyst analyst = AiServices.create(Analyst.class, chatModel);
 
if (analyst.isSpam(message)) return;
Ticket ticket = analyst.triage(message);   // a typed object, no parsing

Compare that to parsing free-form text into a Ticket, with retries for malformed JSON. The library handles schema generation, the instruction and deserialisation.

Enums: the most reliable structured output

An enum return type is exceptionally reliable because the schema lists the only valid answers:

enum Intent { BILLING, TECHNICAL, SALES, OTHER }
 
interface Router {
    @UserMessage("Classify the intent of: {{it}}")
    Intent classify(String message);
}

The model cannot return REFUND_REQUEST — it is not in the enum, so the schema forbids it.

JSON schema mode

On providers that support it, LangChain4j can use strict JSON schema mode, which constrains the output at the API level rather than through prompt instructions:

ChatModel model = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o-mini")
        // Ask the provider to enforce the response schema, not just request it.
        .responseFormat("json_schema")
        .strictJsonSchema(true)
        .build();

This raises structural reliability from "very high with a clear type" to "guaranteed valid against the schema", which matters for high-volume extraction where even a rare parse failure is a real cost.

Validating values

Structured output guarantees the object matches your type. It does not guarantee the values are correct — that is a separate concern:

record DateRange(LocalDate from, LocalDate to) {
    public DateRange {
        // The model returned two valid dates. That does not make them ordered.
        if (to.isBefore(from)) {
            throw new IllegalArgumentException("end date before start date");
        }
    }
}

Handling parse failures

Rare with a clear type and low temperature, but not zero. Handle it:

public Optional<Ticket> triageSafely(String report) {
    try {
        return Optional.of(analyst.triage(report));
    } catch (Exception e) {
        log.warn("triage parse failed for report length {}", report.length(), e);
        return Optional.empty();   // degrade rather than crash the request
    }
}

Designing types the model can fill

The field names are part of the prompt — the model reads them. Design for the model, not only for your database:

  • Descriptive namesprepMinutes extracts better than pm.
  • Flatter structures — deeply nested types are harder to fill correctly.
  • Enums for closed sets — as above.
  • Nullable for genuinely optional fields — with an instruction to leave unknowns null rather than guess.
record Invoice(
        String vendor,
        LocalDate date,
        List<LineItem> items,
        BigDecimal total) {
    record LineItem(String description, int quantity, BigDecimal unitPrice) {}
}

When to use structured output

Use it whenever your code consumes the response — extraction, classification, routing, anything you if or switch on. Use plain text return only when a human reads the output directly. The rule of thumb: if the machine acts on it, type it.

Next

You have completed Phase 2. You know LangChain4j end to end.

Frequently Asked Questions

How does LangChain4j return a typed object instead of text?
Declare the return type on your AI Service interface method — a record, a POJO, an enum, a List or a boolean — and LangChain4j derives a JSON schema from it, instructs the model to produce matching JSON, and deserialises the response into that type. You write no parser; the return type drives everything.
Can LangChain4j guarantee valid JSON from the model?
With a provider that supports JSON schema mode, LangChain4j can constrain the output at the API level so it is structurally valid against your type. On providers without that, it relies on prompt instructions plus parsing, which is highly reliable with a clear type and low temperature but not absolutely guaranteed — so handle the parse-failure path.
Should I use an enum or a string for a category field?
Always an enum when the values are known. The schema enumerates the permitted values, so the model cannot invent a category outside your set. A string field invites drift — VERY_POSITIVE instead of POSITIVE — that an enum makes impossible.
How do I validate the values, not just the structure?
Structured output guarantees the shape, not the correctness of the values. Use a record's compact constructor or a validation step to check business rules — a date range that is ordered, an amount that is non-negative. The model can return a schema-valid object that is still wrong.

Related tutorials