How to Use Jackson Annotations for LLM-Friendly Domain Objects in Embabel

Annotate your Java domain classes with @JsonClassDescription, @JsonCreator, @JsonProperty, and @JsonPropertyDescription to enable Embabel to generate precise JSON schemas for LLM prompts and reliably deserialize model outputs back into immutable Java objects.

The embabel/embabel-agent repository provides a framework for integrating large language models into Java applications through structured output parsing. When you use Jackson annotations for LLM-friendly domain objects in Embabel, the framework can automatically generate JSON schemas from your classes, embed descriptive metadata into LLM prompts, and safely reconstruct objects from model-generated JSON.

Core Jackson Annotations for Embabel Domain Objects

Embabel relies on four primary Jackson annotations to define the structure and documentation of domain classes. These annotations control both the prompt generation phase and the subsequent deserialization of LLM responses.

@JsonClassDescription

The @JsonClassDescription annotation supplies human-readable documentation that Embabel injects directly into LLM prompts. This description tells the model what the object represents and how it should be constructed.

In src/test/java/com/embabel/example/simple/horoscope/java/Writeup.java, the class-level description clarifies the object's purpose:

@JsonClassDescription("Writeup relating to a person's horoscope and relevant news")
public class Writeup {
    // ...
}

@JsonCreator

The @JsonCreator annotation marks the constructor or factory method that Jackson (and Embabel) must use for deserialization. When applied to a constructor, it signals that the LLM must output a JSON object whose keys match the constructor parameters.

As shown in src/test/java/com/embabel/example/simple/horoscope/java/StarPerson.java:

@JsonCreator
public StarPerson(
    @JsonProperty("name") String name,
    @JsonPropertyDescription("Star sign") @JsonProperty("sign") String sign) {
    this.name = name;
    this.sign = sign;
}

@JsonProperty

The @JsonProperty annotation declares the JSON field name for a Java property and optionally marks it as required. Embabel uses this mapping to validate LLM output and generate accurate JSON schemas for the prompt.

@JsonPropertyDescription

The @JsonPropertyDescription annotation adds semantic context to individual fields. This description appears in the LLM prompt, helping the model understand the expected content and format of each property.

How Embabel Processes Jackson Annotations

Embabel scans annotated domain classes through three distinct phases to ensure reliable structured output from LLMs.

Prompt Generation – When requesting a domain object from an LLM, Embabel introspects the class for Jackson annotations. It constructs a JSON schema that incorporates field names, required flags from @JsonProperty, and descriptions from both @JsonClassDescription and @JsonPropertyDescription.

LLM Guidance – The generated schema is embedded directly into the prompt, instructing the model exactly which fields to emit and what each field represents. This reduces hallucination and format errors in the model response.

Deserialization – After receiving JSON from the LLM, Embabel delegates to Jackson. Using the @JsonCreator constructor and @JsonProperty mappings, Jackson instantiates the object reliably, even when working with immutable final fields.

Practical Implementation Example

The horoscope test package in embabel-agent-api demonstrates the complete pattern for creating LLM-friendly domain objects.

First, define a simple immutable object with class-level description, as seen in src/test/java/com/embabel/example/simple/horoscope/java/Writeup.java:

@JsonClassDescription("Writeup relating to a person's horoscope and relevant news")
public class Writeup {

    private final String text;

    @JsonCreator
    public Writeup(@JsonProperty("text") String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

For objects requiring property-level documentation, reference src/test/java/com/embabel/example/simple/horoscope/java/StarPerson.java:

@JsonClassDescription("Person with astrology details")
public class StarPerson {

    private final String name;
    private final String sign;

    @JsonCreator
    public StarPerson(
        @JsonProperty("name") String name,
        @JsonPropertyDescription("Star sign") @JsonProperty("sign") String sign) {
        this.name = name;
        this.sign = sign;
    }

    // Getters omitted for brevity
}

Embabel uses these definitions to construct prompts that guide the LLM toward valid JSON outputs. The integration tests in LLMOpenAiGuardRailsIntegrationIT.java validate that models respect these Jackson-annotated schemas when operating in structured output mode.

Best Practices for LLM-Friendly Domain Models

Follow these patterns to maximize reliability when using Jackson annotations with Embabel:

  • Prefer immutable fields – Declare fields as final and use a @JsonCreator constructor. This pattern ensures thread safety and works seamlessly with Jackson's deserialization.
  • Document every property – Apply @JsonPropertyDescription to each significant field. Rich descriptions improve the LLM's understanding of semantic requirements and reduce invalid outputs.
  • Mark required fields explicitly – Use @JsonProperty(required = true) on mandatory parameters to enforce presence and prevent deserialization failures from incomplete model responses.
  • Keep descriptions concise – Class and property descriptions appear directly in LLM prompts. Overly verbose text consumes context window space and may degrade model performance.

Summary

  • @JsonClassDescription provides high-level context about the domain object's purpose for LLM prompts.
  • @JsonCreator designates the constructor Jackson uses to rebuild objects from JSON, supporting immutable designs.
  • @JsonProperty maps JSON fields to Java properties and validates required fields.
  • @JsonPropertyDescription adds field-level documentation that guides the LLM toward correct value selection.
  • The embabel/embabel-agent framework uses these annotations to automate schema generation, prompt enrichment, and type-safe deserialization.

Frequently Asked Questions

How does Embabel handle immutable domain objects with Jackson?

Embabel leverages the @JsonCreator annotation to instantiate immutable classes. By annotating a constructor that accepts all required fields as parameters, Jackson can populate final fields without requiring setters. This pattern appears throughout the test fixtures in JavaStructuredOutputFixtures.java, demonstrating reliable deserialization of immutable structures.

Which Jackson annotation is most important for improving LLM output quality?

While all four annotations contribute to reliability, @JsonPropertyDescription provides the most direct impact on output quality. By describing the semantic meaning of each field, you guide the LLM to generate contextually appropriate values rather than generic placeholders.

Can I use standard JavaBeans setters instead of @JsonCreator?

Yes, Jackson supports setter-based deserialization, but Embabel strongly recommends the @JsonCreator pattern with immutable fields. This approach prevents partial construction states and ensures that objects remain valid after deserialization, which is critical when handling potentially malformed JSON from LLM outputs.

Where can I find integration tests demonstrating structured output with OpenAI models?

The LLMOpenAiGuardRailsIntegrationIT.java file in the embabel-agent-openai-autoconfigure module contains comprehensive integration tests. These tests demonstrate how Embabel configures OpenAI models to respect Jackson-annotated schemas and validate that the generated JSON conforms to the expected domain object structure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →