<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julia Denysova</title>
    <description>The latest articles on DEV Community by Julia Denysova (@julia_denysova).</description>
    <link>https://dev.to/julia_denysova</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4022994%2Fd0e22106-80c9-4ecd-a97d-4f6b55635e5a.jpg</url>
      <title>DEV Community: Julia Denysova</title>
      <link>https://dev.to/julia_denysova</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julia_denysova"/>
    <language>en</language>
    <item>
      <title>Spring AI Token Usage: Measure Cost Before You Pick a Model — LLM Cost Control 1/4</title>
      <dc:creator>Julia Denysova</dc:creator>
      <pubDate>Thu, 30 Jul 2026 15:26:31 +0000</pubDate>
      <link>https://dev.to/julia_denysova/spring-ai-token-usage-measure-cost-before-you-pick-a-model-llm-cost-control-14-41fo</link>
      <guid>https://dev.to/julia_denysova/spring-ai-token-usage-measure-cost-before-you-pick-a-model-llm-cost-control-14-41fo</guid>
      <description>&lt;p&gt;&lt;em&gt;Cutting LLM costs in Spring AI starts with two choices: which model answers a request, and what defaults your &lt;code&gt;ChatClient&lt;/code&gt; adds to every one it sends.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Neither is worth changing until you can see where the tokens go. That is why this article starts with measurement.&lt;/p&gt;

&lt;p&gt;This is Part 1 of four, and it covers the first three of ten cost drivers. Driver #0 tells you where the money actually goes; #1 and #2 are the two decisions that shape every request your application sends. The remaining seven attach to what you build here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A note on the numbers: where a price ratio matters for the argument (input vs. output, cache read vs. write), this series quotes real July 2026 list prices with a link. All other examples use a flat rate of $1 per million input tokens, so you can redo the calculation with your own provider's price sheet. You should do that, because these prices change every few months.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Driver #0 — Spring AI observability measurement: you cannot cut what you cannot see
&lt;/h2&gt;

&lt;p&gt;Provider invoices and usage dashboards usually show your spending by model and by token type — input, output, and cached. That is useful, but it is not enough. The numbers cannot tell you which feature, client, or advisor inside your application was responsible for that usage.&lt;/p&gt;

&lt;p&gt;Spring AI integrates with &lt;a href="https://docs.spring.io/spring-ai/reference/observability/index.html" rel="noopener noreferrer"&gt;Spring Boot's Micrometer-based observability&lt;/a&gt; to fill this gap. Its core AI components automatically emit that data. &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/chat/model/ChatModel.html" rel="noopener noreferrer"&gt;&lt;code&gt;ChatModel&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/embedding/EmbeddingModel.html" rel="noopener noreferrer"&gt;&lt;code&gt;EmbeddingModel&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/image/ImageModel.html" rel="noopener noreferrer"&gt;&lt;code&gt;ImageModel&lt;/code&gt;&lt;/a&gt; implementations (support varies by provider) publish model-level observations, including token usage where available. &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/chat/client/ChatClient.html" rel="noopener noreferrer"&gt;&lt;code&gt;ChatClient&lt;/code&gt;&lt;/a&gt; (including advisors) and &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/vectorstore/VectorStore.html" rel="noopener noreferrer"&gt;&lt;code&gt;VectorStore&lt;/code&gt;&lt;/a&gt; report execution observations and traces rather than token usage metrics.&lt;/p&gt;

&lt;p&gt;Each metric includes built-in tags, such as the model name and token type. These tags separate models and providers, but not callers: every request to the same model carries the same tag values, so they cannot tell two features apart on their own. Spring AI marks tags as low- or high-cardinality: low-cardinality tags go on both metrics and traces, high-cardinality tags go on traces only. To attribute usage to a &lt;a href="https://docs.spring.io/spring-framework/reference/integration/observability.html#observability.config.conventions" rel="noopener noreferrer"&gt;specific feature or client&lt;/a&gt;, add your own low-cardinality tags. Keep those values few and stable, not one per user or request. For more on how tags work, and how to keep their number under control, see &lt;a href="https://docs.micrometer.io/micrometer/reference/concepts/naming.html" rel="noopener noreferrer"&gt;Micrometer's documentation on tags and naming&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this data in place, you can answer the questions the later drivers raise. Is history growth &lt;em&gt;(Part 2)&lt;/em&gt; really increasing costs? Watch prompt token usage over time. Did the tool-search advisor &lt;em&gt;(Part 3)&lt;/em&gt; reduce the amount of context sent to the model? Compare prompt token usage before and after enabling it. Is a reasoning model becoming more expensive than expected &lt;em&gt;(Part 2)&lt;/em&gt;? Output token trends can reveal unexpectedly long responses and rising generation costs.&lt;/p&gt;

&lt;p&gt;Two more steps make this setup safer, not just more informative. First, create an alert for sudden increases in token usage or request volume. A bug that puts an LLM call inside a loop can burn through your budget fast, and an alert catches it within minutes instead of at the end of the month. Second, configure provider-side spending controls where available, such as budgets, quotas, or usage limits. This is a final safeguard that no application framework can enforce.&lt;/p&gt;

&lt;p&gt;Treat observability as step zero. Set it up before optimising anything else, so every later change has a measurable before-and-after comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Driver #1 — Model choice: stop paying flagship prices for mini-model work
&lt;/h2&gt;

&lt;p&gt;The fastest way to overspend on an LLM application is to send every request to the most capable model you can find. Flagship and budget models on the same provider's price sheet can differ tenfold in price per token, or more. But a large share of typical workloads — classification, extraction, routing, short summaries — passes the quality bar on the smaller model already. Sending those requests to a flagship model increases cost without improving the outcome.&lt;/p&gt;

&lt;p&gt;Price is only one factor, though. Models also differ in what they can do: some accept images or audio, some are built for tool calling or strict structured output, some reason step by step while others just answer directly. The rule is the same either way: pick the smallest model that covers what the task actually needs. Do not pay for image support on a text-only pipeline, and do not pay for a reasoning model just to reformat JSON. Requirements change over time, so treat your model choice as temporary, not final. Providers release cheaper and better models every few months, and a task that needed the flagship model yesterday often works fine on the mini model next quarter.&lt;/p&gt;

&lt;p&gt;This only pays off if switching models is easy. Spring AI's answer is that the model is configuration, not architecture. &lt;code&gt;ChatClient&lt;/code&gt; does not depend on any one provider or model: your service code does not need to know which model answers, portable options like &lt;code&gt;maxTokens&lt;/code&gt; and &lt;code&gt;temperature&lt;/code&gt; carry over between models, and provider-specific settings stay isolated in one options object. &lt;a href="https://docs.spring.io/spring-ai/reference/index.html" rel="noopener noreferrer"&gt;Spring AI 2.0&lt;/a&gt; makes this explicit: default options set on a &lt;code&gt;ChatClient&lt;/code&gt; act as a partial "delta" over the model's start-up configuration. The framework signals this same idea in its own defaults: the OpenAI integration in 2.0 &lt;a href="https://spring.io/blog/2025/12/11/spring-ai-2-0-0-M1-available-now#key-functional-areas-enhanced" rel="noopener noreferrer"&gt;defaults to &lt;code&gt;gpt-5-mini&lt;/code&gt;&lt;/a&gt;, not the flagship model. A good pattern is one client per cost tier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatClients&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@Primary&lt;/span&gt;
    &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;cheapClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5-mini"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;flagshipClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ticket classifier feature could inject &lt;code&gt;@Qualifier("cheapClient") ChatClient&lt;/code&gt;, while a contract-analysis feature injects the flagship one. Two details matter here. First, build clients from the auto-configured builder, not from a raw &lt;code&gt;ChatClient.builder(chatModel)&lt;/code&gt;. The auto-configured builder already has observability wired in, and the token metrics from Driver #0 depend on that. Second, this example covers two tiers on one provider. If you mix vendors in the same application, create a separate &lt;code&gt;ChatClient&lt;/code&gt; from each auto-configured &lt;code&gt;ChatModel&lt;/code&gt; instead. Either way, the size of the change stays small: swapping models is a property change, and swapping vendors just adds a starter dependency plus properties. No code changes, no prompt rewrites, no new SDK to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Driver #2 — One shared client: every request carries every default
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;ChatClient&lt;/code&gt; default is not free. Everything attached to the client — system prompt, tools, memory advisor — is included in every request that client sends. One shared "do-everything" client means a simple 10-token call (e.g. a classifier feature) carries the same 2,000-token system prompt, tool schemas, and conversation history as your most complex feature. If the classifier runs 50,000 times a month, that is 100 million input tokens of extra content it never actually needs — $100 a month at the example rate, for nothing.&lt;/p&gt;

&lt;p&gt;Spring AI's unit of cost control is the client, not the whole application. &lt;a href="https://docs.spring.io/spring-ai/reference/api/chatclient.html" rel="noopener noreferrer"&gt;&lt;code&gt;ChatClient.Builder&lt;/code&gt;&lt;/a&gt; is auto-configured as a prototype bean, so each task can build its own client with only the defaults that task needs: &lt;code&gt;.defaultSystem()&lt;/code&gt; for the prompt, &lt;code&gt;.defaultOptions()&lt;/code&gt; for model and token settings, &lt;code&gt;.defaultAdvisors()&lt;/code&gt; and &lt;code&gt;.defaultTools()&lt;/code&gt; for the rest. Every later driver in this series — memory windows, RAG advisors, tool search — is attached at exactly this level. This is why building per-task clients should come before the drivers that follow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskClients&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;classifierClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultSystem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Classify the ticket. Reply with one word: BILLING, TECH, or OTHER."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5-mini"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;supportChatClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ChatMemory&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultSystem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prompts/support.st"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MessageChatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note the &lt;a href="https://docs.spring.io/spring-ai/reference/upgrade-notes.html" rel="noopener noreferrer"&gt;Spring AI 2.0 change&lt;/a&gt;: &lt;code&gt;.defaultOptions()&lt;/code&gt; and the per-call &lt;code&gt;.options()&lt;/code&gt; now take a &lt;code&gt;ChatOptions.Builder&lt;/code&gt;, not a fully built instance. This builder is merged with the model's defaults before the first advisor runs, and only the fields you explicitly set override them. That merge lets you override only the options you need without replacing the model's configured defaults:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;classifierClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// override for a yes/no case&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: each request pays only for the defaults its own task actually declared. That is a requirement for every control that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Drivers #1 and #2 decide which model answers a request and which client sends it; Driver #0 shows you what those decisions cost. You now have one client per task, each carrying only the defaults that task needs, and the token metrics to prove it.&lt;/p&gt;

&lt;p&gt;None of that limits the size of a single request. A reasoning model can bill more hidden thinking than visible answer. A support conversation resends its whole history on every turn. The same system prompt goes out thousands of times a day at full price, unless you structure it so the provider can cache it.&lt;/p&gt;

&lt;p&gt;Part 2 covers those three. Each one is a limit or an advisor you attach to the clients you have just built. &lt;/p&gt;

&lt;p&gt;Part 2 arrives in August 2026.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to Reduce LLM Costs in Spring AI 2.0: 10 Practical Controls</title>
      <dc:creator>Julia Denysova</dc:creator>
      <pubDate>Thu, 30 Jul 2026 15:26:02 +0000</pubDate>
      <link>https://dev.to/julia_denysova/how-to-reduce-llm-costs-in-spring-ai-20-10-practical-controls-2jj7</link>
      <guid>https://dev.to/julia_denysova/how-to-reduce-llm-costs-in-spring-ai-20-10-practical-controls-2jj7</guid>
      <description>&lt;p&gt;&lt;em&gt;Spring AI's defaults are built for a fast start; they do not guarantee a low monthly cost. Shipping an LLM feature is easy — making it cost-efficient is not. This series shows the spots where money leaks, along with the control that closes each one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spring.io/blog/2026/06/12/spring-ai-2-0-0-GA-available-now" rel="noopener noreferrer"&gt;Spring AI 2.0 reached GA on 12 June 2026&lt;/a&gt;. It needs &lt;a href="https://docs.spring.io/spring-boot/index.html" rel="noopener noreferrer"&gt;Spring Boot 4&lt;/a&gt;, moves the tool-calling loop out of the &lt;a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/chat/model/ChatModel.html" rel="noopener noreferrer"&gt;&lt;code&gt;ChatModel&lt;/code&gt;&lt;/a&gt;, adds tool search, and extends structured outputs. Tool search and the extended structured-output controls point in the same direction: they determine how many tokens your application sends and receives.&lt;/p&gt;

&lt;p&gt;The bill grows quietly. A chatbot with a 2,000-token system prompt, run 100,000 times a month, sends 200 million tokens of the same text. At an example rate of $1 per million input tokens, that is $200 a month — before a single user message. Then add conversation history, which is sent in full on every turn. Add retrieved RAG documents and the JSON schema of every registered tool. The input side can grow 10× with no change in traffic at all. Output tokens cost several times more per token than input tokens, and reasoning models bill their hidden "thinking" as output too.&lt;/p&gt;

&lt;p&gt;The provider sets the prices. The framework gives you controls that can reduce the number of tokens you pay for.&lt;/p&gt;

&lt;p&gt;This series works through ten cost drivers, numbered #0 to #9. Each one is a place where tokens repeat or grow without anyone deciding they should, and each comes with the Spring AI control that cuts it. They are spread across four parts. Part 1 is live. Parts 2 to 4 follow in August 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dev.to/julia_denysova/spring-ai-token-usage-measure-cost-before-you-pick-a-model-llm-cost-control-14-41fo"&gt;Part 1 — Token Usage: Measure Cost Before You Pick a Model (Drivers #0–#2)&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Provider dashboards show what you spent, but not which feature spent it. Spring AI's observability closes that gap, and from there you can match each model to its task and stop features from carrying defaults they never needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2 — Prompt Caching and Chat Memory: Where the Tokens Go (Drivers #3–#5)
&lt;/h2&gt;

&lt;p&gt;This part covers limiting response length, bounding how much conversation history you resend, and structuring prompts so that provider caching actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3 — RAG and Tool Calling: Paying for Context You Don't Use (Drivers #6–#7)
&lt;/h2&gt;

&lt;p&gt;Retrieved documents and tool definitions are added to every request, whether the model needs them or not. This part covers retrieving less, cleaning up what was retrieved, and sending tool schemas only when they are relevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4 — Retries and Embeddings: Failed Answers and Full Re-indexes (Drivers #8–#9)
&lt;/h2&gt;

&lt;p&gt;A failed response is billed in full, and retrying it resends the entire context. This part covers preventing malformed output at the provider level, then the indexing side: embedding model choice, vector size, batching, and re-embedding only what has changed.&lt;/p&gt;




&lt;p&gt;Start with &lt;a href="https://dev.to/julia_denysova/spring-ai-token-usage-measure-cost-before-you-pick-a-model-llm-cost-control-14-41fo"&gt;Part 1&lt;/a&gt;. Every later driver asks you to trade something — context, memory, response length — and per-feature metrics are what tell you which trade is worth making. Part 1 also covers the cheapest change available: sending routine work to a smaller model, which usually needs no more than a property change.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>ai</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
