<?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: Mohammad Jamalianpour</title>
    <description>The latest articles on DEV Community by Mohammad Jamalianpour (@jamalianpour).</description>
    <link>https://dev.to/jamalianpour</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1869342%2F7fdd488d-6c32-4f01-8825-95107be6cc00.png</url>
      <title>DEV Community: Mohammad Jamalianpour</title>
      <link>https://dev.to/jamalianpour</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jamalianpour"/>
    <language>en</language>
    <item>
      <title>Semantic LLM Cache: Vector-Based Caching for Java (Spring Boot)</title>
      <dc:creator>Mohammad Jamalianpour</dc:creator>
      <pubDate>Wed, 04 Feb 2026 07:54:59 +0000</pubDate>
      <link>https://dev.to/jamalianpour/semantic-llm-cache-vector-based-caching-for-java-spring-boot-g92</link>
      <guid>https://dev.to/jamalianpour/semantic-llm-cache-vector-based-caching-for-java-spring-boot-g92</guid>
      <description>&lt;h2&gt;
  
  
  How Vector Embeddings Can Slash Your LLM API Costs by 80%
&lt;/h2&gt;

&lt;p&gt;If you're building applications powered by large language models, you've probably noticed something painful: your API bill keeps growing. Not because your application is doing anything particularly complex, but because users keep asking variations of the same questions, and each variation triggers a fresh API call.&lt;/p&gt;

&lt;p&gt;This is the hidden tax of working with LLMs, and today I want to show you how to solve it with semantic caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Traditional Caching
&lt;/h2&gt;

&lt;p&gt;Let's say you're building a customer support chatbot. A user asks, "How do I reset my password?" Your application calls the OpenAI API or any other LLMs provider, gets a response, and you wisely decide to cache it. Smart move.&lt;/p&gt;

&lt;p&gt;But then another user comes along and asks, "I forgot my password, what should I do?" Your cache looks at this query, compares it character by character with what's stored, and finds no match. So off goes another API request. You pay again and the user waits again.&lt;/p&gt;

&lt;p&gt;Here's what traditional caching sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"How do I reset my password?" ≠ "I forgot my password, what should I do?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different strings. Cache miss. End of story.&lt;/p&gt;

&lt;p&gt;Now imagine this happening hundreds or thousands of times per day across all the different ways people phrase the same questions. Your cache hit rate stays frustratingly low, and your costs stay frustratingly high.&lt;/p&gt;

&lt;p&gt;The fundamental issue is that traditional caching operates on syntax (the exact sequence of characters) when what we really care about is semantics, the meaning behind those characters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter Semantic Caching
&lt;/h2&gt;

&lt;p&gt;What if your cache could understand that "How do I reset my password?" and "I forgot my password, what should I do?" are essentially asking the same thing? What if it could recognize meaning, not just text?&lt;/p&gt;

&lt;p&gt;This is exactly what semantic caching does. Instead of storing queries as raw strings, it converts them into vector embeddings mathematical representations that capture the semantic meaning of text. When a new query arrives, the system converts it to a vector and searches for similar vectors in the cache. If it finds one above a certain similarity threshold, it returns the cached response without ever touching the LLM API.&lt;/p&gt;

&lt;p&gt;The magic happens because embedding models are trained to place semantically similar text close together in vector space. "Reset my password" and "forgot my password" end up as neighboring vectors, even though they share few common words.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                      User Query                                 │
│              "What's the capital of France?"                    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                   L1 Cache (Exact Match)                        │
│                    Caffeine In-Memory                           │
│                      &amp;lt; 1ms lookup                               │
└─────────────────────────────────────────────────────────────────┘
                              │ Miss
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Embedding Provider                           │
│            Convert text → [0.023, -0.891, 0.445, ...]           │
│               (ONNX, OpenAI, Ollama, Azure)                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                 L2 Cache (Semantic Search)                      │
│              Vector similarity search in storage                │
│              (Redis, Elasticsearch, In-Memory)                  │
│                                                                 │
│   Cached: "Tell me France's capital" → similarity: 0.94 ✓       │
└─────────────────────────────────────────────────────────────────┘
                              │ Hit!
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Return: "Paris"                             │
│                  Total time: ~10-50ms                           │
│                  Cost: $0.00                                    │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dual-level architecture is key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L1 Cache: Exact matches with sub-millisecond latency&lt;/li&gt;
&lt;li&gt;L2 Cache: Semantic matches with millisecond latency (Vector storage)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introducing Semantic LLM Cache for Java
&lt;/h2&gt;

&lt;p&gt;Semantic LLM Cache is an open-source library that brings this capability to Java and Spring Boot applications. It handles the complexity of embedding generation, vector storage, and similarity search, exposing a simple interface that feels native to Spring developers.&lt;/p&gt;

&lt;p&gt;Let me walk you through how it works and how to integrate it into your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;First, add the dependencies to your project. You'll need three components: the Spring Boot starter, a storage backend, and an embedding provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Jamalianpour/semantic-llm-cache" rel="noopener noreferrer"&gt;https://github.com/Jamalianpour/semantic-llm-cache&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- The core Spring Boot integration --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.jamalianpour&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;semantic-llm-cache-spring-boot-starter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.0.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Storage: where vectors live (starting with in-memory for simplicity) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.jamalianpour&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;semantic-llm-cache-storage-memory&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.0.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Embeddings: how text becomes vectors (using OpenAI here) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.jamalianpour&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;semantic-llm-cache-embedding-openai&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.0.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component is modular by design. You can swap storage backends or embedding providers without changing your application code. This matters because your needs will evolve you might start with in-memory storage for development and move to Redis or Elasticsearch for production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Next, configure the cache in your &lt;code&gt;application.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai&lt;/span&gt;
    &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;        &lt;span class="c1"&gt;# Your OpenAI API key&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;     &lt;span class="c1"&gt;# Cost-effective embedding model&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;                       &lt;span class="c1"&gt;# In-memory for development&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;similarity-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.92&lt;/span&gt;         &lt;span class="c1"&gt;# How similar queries must be (0.0 to 1.0)&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;                           &lt;span class="c1"&gt;# How long cached responses live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;similarity-threshold&lt;/code&gt; parameter is crucial. Setting it to 0.92 means a query must be at least 92% similar to a cached query to be considered a match. Too low, and you'll return incorrect responses for genuinely different questions. Too high, and you'll miss opportunities to serve from cache. I've found 0.90 to 0.95 works well for most conversational AI applications, but you should experiment with your specific use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Annotation
&lt;/h3&gt;

&lt;p&gt;Now comes the elegant part. To enable semantic caching on any method, simply add the &lt;code&gt;@SemanticCache&lt;/code&gt; annotation:&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;@Service&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;CustomerSupportService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;OpenAiClient&lt;/span&gt; &lt;span class="n"&gt;openAiClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomerSupportService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OpenAiClient&lt;/span&gt; &lt;span class="n"&gt;openAiClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openAiClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openAiClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@SemanticCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"support"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// Isolates this cache from others&lt;/span&gt;
        &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt;         &lt;span class="c1"&gt;// Override default threshold if needed&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;answerQuestion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This only executes on cache misses&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;openAiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;That's genuinely all the code you need. When &lt;code&gt;answerQuestion&lt;/code&gt; is called, the library intercepts the call, generates an embedding for the question, searches for similar cached entries, and either returns a cached response or proceeds to call your method and cache the result.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;namespace&lt;/code&gt; parameter creates logical separation between different caches. Your FAQ responses shouldn't interfere with your product recommendation cache, even if someone asks a similar-sounding question in both contexts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding What Happens Under the Hood
&lt;/h2&gt;

&lt;p&gt;To use semantic caching effectively, it helps to understand the flow of operations.&lt;/p&gt;

&lt;p&gt;When a query arrives, the library first generates a vector embedding. If you're using OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt; model, this produces a 1536-dimensional vector — essentially a list of 1536 numbers that mathematically represent the meaning of your text. This embedding generation takes around 50-100ms and costs a fraction of a cent.&lt;/p&gt;

&lt;p&gt;Next, the library searches the vector storage for similar embeddings. It uses cosine similarity, which measures the angle between two vectors. Identical vectors have a similarity of 1.0. Completely unrelated vectors approach 0.0. The search returns the most similar cached entry along with its similarity score.&lt;/p&gt;

&lt;p&gt;If the similarity exceeds your threshold, you have a cache hit. The library returns the stored response, and your LLM API is never called. Total latency: typically under 10ms for in-memory storage, under 50ms for Redis or Elasticsearch.&lt;/p&gt;

&lt;p&gt;If the similarity falls below your threshold (or no similar entries exist), you have a cache miss. Your method executes normally, calling the LLM API. Before returning, the library caches both the query embedding and the response for future use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing Your Storage Backend
&lt;/h2&gt;

&lt;p&gt;The library supports three storage backends, each suited to different scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In-Memory Storage&lt;/strong&gt; keeps everything in the JVM heap using a ConcurrentHashMap. It's fast and requires no external infrastructure, making it perfect for development, testing, and small applications. The obvious limitation is that data disappears when your application restarts, and it doesn't work across multiple application instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Redis Storage&lt;/strong&gt; uses Redis Stack's vector search capabilities. It provides persistence, sub-millisecond latency, and works across distributed deployments. If you're already running Redis, this is often the natural choice for production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need Redis Stack (not plain Redis) because it includes the RediSearch module with vector similarity search. The easiest way to get started is with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; redis-stack &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 redis/redis-stack:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Elasticsearch Storage&lt;/strong&gt; is designed for large-scale deployments. It handles millions of vectors efficiently and integrates well if you're already using Elasticsearch for search or logging. The HNSW algorithm provides approximate nearest neighbor search that scales remarkably well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
    &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;uris&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:9200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Choosing Your Embedding Provider
&lt;/h2&gt;

&lt;p&gt;Vector quality matters. Better embeddings lead to more accurate similarity matching, which means higher cache hit rates and fewer false positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI&lt;/strong&gt; provides excellent embeddings with minimal setup. The &lt;code&gt;text-embedding-3-small&lt;/code&gt; model offers a good balance of quality and cost at $0.02 per million tokens. For applications where embedding quality is critical, &lt;code&gt;text-embedding-3-large&lt;/code&gt; provides measurably better results at a higher price point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure OpenAI&lt;/strong&gt; offers the same models through Azure's infrastructure, which matters for enterprises with compliance requirements or existing Azure investments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; lets you run embedding models locally. This eliminates embedding costs entirely and keeps all data on your infrastructure. The &lt;code&gt;nomic-embed-text&lt;/code&gt; model produces good quality embeddings and runs efficiently on modest hardware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ollama&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;
    &lt;span class="na"&gt;ollama-base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ONNX&lt;/strong&gt; goes a step further, running models directly in your JVM without any external service. This is ideal for offline deployments or when you want to minimize dependencies. The library includes several pre-trained models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;semantic-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;onnx&lt;/span&gt;
    &lt;span class="na"&gt;onnx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pretrained-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALL_MINILM_L6_V2&lt;/span&gt;   &lt;span class="c1"&gt;# Fast and lightweight&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade-off with local models is generally quality. OpenAI's embeddings tend to outperform open-source alternatives, though the gap has been narrowing. For many applications, the cost savings of local embeddings outweigh the modest quality difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Tenant Caching
&lt;/h2&gt;

&lt;p&gt;Real applications often serve multiple users or organizations, and you typically don't want User A's cached responses served to User B. The library handles this through context keys.&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;@SemanticCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"support"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contextKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"#userId"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;    &lt;span class="c1"&gt;// Isolate cache by user&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;answerQuestion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&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;openAiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;The &lt;code&gt;contextKeys&lt;/code&gt; parameter accepts SpEL expressions that evaluate to isolation keys. The cache effectively becomes partitioned — a query from User A only matches cached entries from User A's previous queries.&lt;/p&gt;

&lt;p&gt;You can combine multiple context keys for more complex isolation:&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;@SemanticCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"support"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contextKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"#tenantId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"#department"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;answerQuestion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;department&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;openAiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;h2&gt;
  
  
  Cache Eviction
&lt;/h2&gt;

&lt;p&gt;When underlying data changes, you need to invalidate cached responses. The &lt;code&gt;@SemanticCacheEvict&lt;/code&gt; annotation handles this:&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;@SemanticCacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"faq"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#topic"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;    &lt;span class="c1"&gt;// Evict entries similar to this topic&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateFaqContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;newContent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;faqRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newContent&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;This is particularly powerful because eviction is also semantic. Updating the "password reset" FAQ entry will evict cached responses for "reset password," "forgot password," and other similar queries — exactly the behavior you want.&lt;/p&gt;

&lt;p&gt;For bulk updates, you can clear an entire namespace:&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;@SemanticCacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"faq"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allEntries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;rebuildAllFaqs&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Clears all cached FAQ responses&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Using Without Spring Boot
&lt;/h2&gt;

&lt;p&gt;While the library is optimized for Spring Boot, the core components work in any Java application:&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="c1"&gt;// Create embedding provider&lt;/span&gt;
&lt;span class="nc"&gt;EmbeddingProvider&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAiEmbeddingFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create storage backend&lt;/span&gt;
&lt;span class="nc"&gt;VectorStorage&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RedisVectorStorageFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"redis://localhost:6379"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
    &lt;span class="mi"&gt;1536&lt;/span&gt;    &lt;span class="c1"&gt;// Dimensions must match embedding model&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Build the cache&lt;/span&gt;
&lt;span class="nc"&gt;SemanticCache&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SemanticCache&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;embeddingProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CacheConfig&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;similarityThreshold&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofHours&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&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="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Use it directly&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"How do I reset my password?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"To reset your password..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CacheHit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"I forgot my password"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cache hit! Similarity: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Response: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;response&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;This programmatic API gives you full control and works with any framework or no framework at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Semantic caching represents a fundamental shift in how we think about caching for AI applications. By operating on meaning rather than text, it unlocks cache hit rates that traditional approaches simply cannot achieve.&lt;/p&gt;

&lt;p&gt;Semantic LLM Cache brings this capability to the Java ecosystem with a clean, modular design that respects how Spring developers build applications. Whether you're building chatbots, FAQ systems, RAG applications, or any other LLM-powered feature, semantic caching can meaningfully reduce your costs and improve response times.&lt;/p&gt;

&lt;p&gt;The library is open source and available on GitHub. Contributions, feedback, and feature requests are always welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Jamalianpour/semantic-llm-cache" rel="noopener noreferrer"&gt;https://github.com/Jamalianpour/semantic-llm-cache&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, consider giving the repository a star. It helps others discover the project and motivates continued development.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>llm</category>
      <category>openai</category>
    </item>
    <item>
      <title>Flutter Build Directories Are Eating Your SSD: Here’s How to Fight Back</title>
      <dc:creator>Mohammad Jamalianpour</dc:creator>
      <pubDate>Fri, 07 Mar 2025 08:54:26 +0000</pubDate>
      <link>https://dev.to/jamalianpour/flutter-build-directories-are-eating-your-ssd-heres-how-to-fight-back-p0f</link>
      <guid>https://dev.to/jamalianpour/flutter-build-directories-are-eating-your-ssd-heres-how-to-fight-back-p0f</guid>
      <description>&lt;p&gt;I've found myself working on several projects at once sometimes 4 or 5 different apps in a single week. Flutter has been a revelation for cross platform development. It has, however, one persistent irritation: the ever growing build folders that take up a lot of disk space.&lt;/p&gt;

&lt;p&gt;If you're anything like me, you've had that moment when your disk space warning goes off, and you find that your Flutter projects are the ones to blame, with not just one or two but several gigabytes of cached builds silently piling up over time. In this article, I'll walk you through the steps of how I solved this problem for myself by creating what I think of as a simple but powerful Dart command-line tool that can automatically clean up Flutter projects in my various development directories.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Flutter's Hungry Build Folders
&lt;/h2&gt;

&lt;p&gt;The build process of Flutter generates a significant number of intermediate files, compiled code, and assets in the build directory of each project. These files are essential during the development and build stages, but they can be safely cleaned up when not in active use.&lt;/p&gt;

&lt;p&gt;The build directory for a single Flutter project can easily swell to hundreds of megabytes or even several gigabytes, especially when you're building for multiple platforms. Working on several projects, of course, adds to this number even more. All told, we could be looking at significant disk space usage.&lt;/p&gt;

&lt;p&gt;Flutter gives a simple opportunity to clean a project by offering the flutter clean command, which removes the build directory and frees up the occupied space.&lt;/p&gt;

&lt;p&gt;Yet the need to clean projects becomes apparent when one has many projects scattered throughout their developer folders and attempts to navigate to each folder just to run the clean command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A Dart CLI Tool for Automated Cleaning
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Jamalianpour" rel="noopener noreferrer"&gt;
        Jamalianpour
      &lt;/a&gt; / &lt;a href="https://github.com/Jamalianpour/f_cleaner" rel="noopener noreferrer"&gt;
        f_cleaner
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A CLI tool to scan directories for Flutter projects and run 'flutter clean' to free up disk space.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Flutter Cleaner (f_cleaner) 🧹&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Dart CLI tool that automatically scans directories for Flutter projects and runs &lt;code&gt;flutter clean&lt;/code&gt; to free up disk space.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Flutter projects accumulate large build directories that consume significant disk space. Manually cleaning each project becomes tedious when you're working on multiple projects.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Solution&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This tool automatically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scans directories to identify Flutter projects&lt;/li&gt;
&lt;li&gt;Calculates build directory sizes&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;flutter clean&lt;/code&gt; on each project&lt;/li&gt;
&lt;li&gt;Reports total space freed&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;You can install flutter cleaner CLI from github repository or pub.dev:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; From Github&lt;/span&gt;
dart pub global activate -sgit https://github.com/Jamalianpour/f_cleaner.git

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; From Pub.dev&lt;/span&gt;
dart pub global activate f_cleaner&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Or install and active it from source code:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Clone the repository&lt;/span&gt;
git clone https://github.com/yourusername/f_cleaner.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; f_cleaner

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install dependencies&lt;/span&gt;
dart pub get

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Activate the CLI tool globally&lt;/span&gt;
dart pub global activate --source path &lt;span class="pl-c1"&gt;.&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Clean Flutter projects in current directory and subdirectories&lt;/span&gt;
f_cleaner
&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Jamalianpour/f_cleaner" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;To solve this problem efficiently, I created a Dart CLI application that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scans specified directories (recursively by default)&lt;/li&gt;
&lt;li&gt;Identifies Flutter projects by detecting the presence of a pubspec.yaml file with Flutter dependencies&lt;/li&gt;
&lt;li&gt;Calculates the size of each project's build directory&lt;/li&gt;
&lt;li&gt;Runs flutter clean on each identified project&lt;/li&gt;
&lt;li&gt;Reports on the total space saved&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach allows me to clean all my Flutter projects with a single command, saving both time and disk space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the CLI Tool
&lt;/h2&gt;

&lt;p&gt;Let's walk through the key components of this solution. Our Dart CLI application consists of two main files: the main Dart code and a pubspec.yaml file for dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;First, I created a new Dart project with the necessary dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f_cleaner&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A CLI tool to scan directories for Flutter projects and run 'flutter clean' to free up disk space.&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;

&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4.0.0'&lt;/span&gt;

&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.6.0&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.9.1&lt;/span&gt;

&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.1.0&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.24.0&lt;/span&gt;

&lt;span class="na"&gt;executables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;f_cleaner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f_cleaner&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;args&lt;/code&gt; package provides command-line argument parsing capabilities, while path helps with cross-platform &lt;code&gt;path&lt;/code&gt; manipulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Functionality
&lt;/h2&gt;

&lt;p&gt;The main code is organized into several key functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Argument Parsing: Handling command-line options for directory selection, recursive scanning, and verbosity&lt;/li&gt;
&lt;li&gt;Flutter Project Detection: Identifying valid Flutter projects&lt;/li&gt;
&lt;li&gt;Directory Scanning: Recursively exploring directories&lt;/li&gt;
&lt;li&gt;Size Calculation: Determining how much space will be freed&lt;/li&gt;
&lt;li&gt;Running Flutter Clean: Executing the command and handling results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a simplified breakdown of the key logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_isFlutterProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check for pubspec.yaml file&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;pubspecFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'pubspec.yaml'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pubspecFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Read pubspec.yaml and check for Flutter dependency&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pubspecFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAsString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'flutter:'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'sdk: flutter'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_calculateDirectorySize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;recursive:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;followLinks:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Ignore errors&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_runFlutterClean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;projectDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Running flutter clean in &lt;/span&gt;&lt;span class="si"&gt;$projectDir&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'flutter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'clean'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nl"&gt;workingDirectory:&lt;/span&gt; &lt;span class="n"&gt;projectDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;runInShell:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main scanning function coordinates these operations and collects the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CleanResults&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;scanAndCleanFlutterProjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;rootDirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;rootDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootDirPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rootDir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Directory does not exist: &lt;/span&gt;&lt;span class="si"&gt;$rootDirPath&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;projectsFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;projectsCleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;spaceFreed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_listDirectories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;recursive:&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_isFlutterProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;projectsFound&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Found Flutter project at: &lt;/span&gt;&lt;span class="si"&gt;${entity.path}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;buildDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'build'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_calculateDirectorySize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buildDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_runFlutterClean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;verbose:&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;projectsCleaned&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="n"&gt;spaceFreed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'✓ Cleaned: &lt;/span&gt;&lt;span class="si"&gt;${entity.path}&lt;/span&gt;&lt;span class="s"&gt; (freed &lt;/span&gt;&lt;span class="si"&gt;${_formatSize(size)}&lt;/span&gt;&lt;span class="s"&gt;)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'✗ Failed to clean: &lt;/span&gt;&lt;span class="si"&gt;${entity.path}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'  Error: &lt;/span&gt;&lt;span class="si"&gt;${result.stderr}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'✗ Error cleaning: &lt;/span&gt;&lt;span class="si"&gt;${entity.path}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'  Error: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'• Skipped: &lt;/span&gt;&lt;span class="si"&gt;${entity.path}&lt;/span&gt;&lt;span class="s"&gt; (no build directory or empty)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CleanResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;projectsFound:&lt;/span&gt; &lt;span class="n"&gt;projectsFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;projectsCleaned:&lt;/span&gt; &lt;span class="n"&gt;projectsCleaned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;spaceFreed:&lt;/span&gt; &lt;span class="n"&gt;spaceFreed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One key optimization in this design is the use of parallel processing with futures. Instead of cleaning each project sequentially, the tool launches multiple cleaning operations concurrently, making the process much faster, especially when dealing with many projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Tool
&lt;/h2&gt;

&lt;p&gt;With the CLI tool complete, usage is straightforward. After installation, it can be run with various options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clean all Flutter projects in current directory and subdirectories&lt;/span&gt;
f_cleaner

&lt;span class="c"&gt;# Clean Flutter projects in a specific directory&lt;/span&gt;
f_cleaner &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/your/flutter/projects

&lt;span class="c"&gt;# Non-recursive scan (only check the specified directory)&lt;/span&gt;
f_cleaner &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/your/flutter/projects &lt;span class="nt"&gt;--no-recursive&lt;/span&gt;

&lt;span class="c"&gt;# Show detailed output&lt;/span&gt;
f_cleaner &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool provides a clear summary after execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Flutter Projects Cleaner 🧹
&lt;span class="o"&gt;==========================&lt;/span&gt;
Scanning directory: /Users/username/development
Recursive scan: Yes

✅ Cleaned: /Users/username/development/project1 &lt;span class="o"&gt;(&lt;/span&gt;freed 2.3 GB&lt;span class="o"&gt;)&lt;/span&gt;
✅ Cleaned: /Users/username/development/project2 &lt;span class="o"&gt;(&lt;/span&gt;freed 1.8 GB&lt;span class="o"&gt;)&lt;/span&gt;
✅ Cleaned: /Users/username/development/clients/project3 &lt;span class="o"&gt;(&lt;/span&gt;freed 3.2 GB&lt;span class="o"&gt;)&lt;/span&gt;
❌ Failed to clean: /Users/username/development/broken_project

Summary
&lt;span class="nt"&gt;-------&lt;/span&gt;
Flutter projects found: 4
Projects cleaned: 3
Approximate space freed: 7.3 GB
Time taken: 5 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits and Results
&lt;/h2&gt;

&lt;p&gt;After implementing this tool in my workflow, I've experienced several benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Significant space savings: Regularly recovering 10–20GB of disk space&lt;/li&gt;
&lt;li&gt;Time efficiency: What used to take manual effort now happens automatically&lt;/li&gt;
&lt;li&gt;Better organization: I no longer avoid cleaning projects due to the hassle&lt;/li&gt;
&lt;li&gt;Development speed: Less time fighting with disk space warnings means more time coding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've been running this tool as part of my weekly maintenance routine, and it has become an essential part of my Flutter development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As Flutter developers, we often focus on building great apps and overlook infrastructure improvements that can enhance our development experience. This simple CLI tool demonstrates how a small investment in automation can solve persistent annoyances and improve productivity.&lt;/p&gt;

&lt;p&gt;The complete source code for this tool is available in the GitHub repository: &lt;a href="https://github.com/Jamalianpour/f_cleaner" rel="noopener noreferrer"&gt;f_cleane&lt;/a&gt; (feel free to contribute or customize it for your needs).&lt;/p&gt;

&lt;p&gt;If you're a Flutter developer managing multiple projects, I encourage you to try this approach or build your own version. The few minutes spent setting up automation will save hours of manual work and gigabytes of disk space in the long run.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>cli</category>
    </item>
    <item>
      <title>DevUtils vs. DevToys vs. OpenDev: Which Developer Utility Tool Is Right for You?</title>
      <dc:creator>Mohammad Jamalianpour</dc:creator>
      <pubDate>Fri, 09 Aug 2024 15:45:36 +0000</pubDate>
      <link>https://dev.to/jamalianpour/devutils-vs-devtoys-vs-opendev-which-developer-utility-tool-is-right-for-you-a4j</link>
      <guid>https://dev.to/jamalianpour/devutils-vs-devtoys-vs-opendev-which-developer-utility-tool-is-right-for-you-a4j</guid>
      <description>&lt;p&gt;As a developer, having the right set of tools can make a significant difference in productivity. DevUtils, DevToys, and Open Dev are three utility tools that aim to streamline your workflow by offering a wide range of functionalities in one place. But with several options available, which one should you choose?&lt;/p&gt;

&lt;p&gt;In this post, we'll objectively compare these three tools across various features to help you decide which one suits your development needs best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DevUtils
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://devutils.com/" rel="noopener noreferrer"&gt;DevUtils&lt;/a&gt; is a macOS-exclusive utility designed for developers. It offers a variety of tools that integrate seamlessly with the macOS environment. With its clean and intuitive interface, DevUtils has become a favorite among macOS developers looking for a powerful and easy-to-use toolkit.&lt;/p&gt;

&lt;h3&gt;
  
  
  DevToys
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://devtoys.app/" rel="noopener noreferrer"&gt;DevToys&lt;/a&gt; is a free, open-source utility for Windows users. It's often referred to as the "Swiss Army knife" for developers, providing a broad range of tools in a single, accessible application. DevToys is particularly appealing due to its simplicity and the wide array of functionalities it offers.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenDev
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/Jamalianpour/open-dev/" rel="noopener noreferrer"&gt;OpenDev&lt;/a&gt; is a newer entry, built with Flutter, making it available across multiple platforms—macOS, Windows, Linux, and Web. It combines many of the features found in DevUtils and DevToys while also introducing unique tools and a cross-platform approach. Open Dev is open-source, inviting contributions from the developer community.&lt;br&gt;
Try It on web &lt;a href="https://jamalianpour.github.io/open-dev/" rel="noopener noreferrer"&gt;OpenDev&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;p&gt;To help you evaluate these tools, here's a side-by-side comparison of their features:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;DevUtils&lt;/th&gt;
&lt;th&gt;DevToys&lt;/th&gt;
&lt;th&gt;Open Dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON Parser and Converter to YAML&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;XML Parser and Converter to JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQL Formatter/Parser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cron Parser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unix Time Converter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;README Helper and Real-Time Viewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer News Based on RSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base64 String/Image Encode/Decode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JWT Debugger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hash Generator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Color Converter (HEX, RGB, HSL)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RegExp Tester&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lorem Ipsum Generator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Password Generator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QR Code Generator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UUID Generator/Decoder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image Extensions Formatter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;URL Encode/Decode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;String Utilities (Case, Replace, etc.)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Markdown to HTML Converter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTML Formatter/Minifier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-Platform Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ (macOS only)&lt;/td&gt;
&lt;td&gt;✅ (macOS, Windows, Linux)&lt;/td&gt;
&lt;td&gt;✅ (macOS, Windows, Linux, web)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Planned Web Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Insights
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Platform Compatibility&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevUtils&lt;/strong&gt; is exclusively available on macOS, making it the ideal choice for developers deeply integrated into the Apple ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevToys&lt;/strong&gt; is designed specifically for Windows, offering a tailored experience for users within the Microsoft environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Dev&lt;/strong&gt; is the most versatile, with support for macOS, Windows, and Linux, catering to developers who work across multiple operating systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Unique Features&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL Formatter/Parser:&lt;/strong&gt; DevToys offers a SQL formatter/parser, which can be a handy tool for developers working with databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron Parser:&lt;/strong&gt; Only Open Dev offers a cron parser, which can be particularly useful for developers dealing with cron jobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README Helper:&lt;/strong&gt; Open Dev also includes a README helper with a real-time viewer, which is a handy feature for developers working on documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown to HTML Converter:&lt;/strong&gt; Both DevUtils and DevToys provide a converter from Markdown to HTML, which is not currently available in Open Dev.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Core Utilities&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;All three tools offer essential developer utilities such as JSON parsing, Unix time conversion, JWT debugging, and color conversion. &lt;/li&gt;
&lt;li&gt;DevToys stands out with additional tools like the SQL Formatter and String Utilities, while Open Dev brings unique features like the Cron Parser and Developer News.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strengths and Weaknesses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DevUtils
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62h27qgsi4opue56rpll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62h27qgsi4opue56rpll.png" alt="DevUtils Screenshot" width="800" height="482"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seamless integration with macOS.&lt;/li&gt;
&lt;li&gt;Clean, intuitive interface.&lt;/li&gt;
&lt;li&gt;Powerful core utilities for everyday developer tasks.&lt;/li&gt;
&lt;li&gt;Includes tools like Markdown to HTML and HTML Formatter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited to macOS, which excludes users on other platforms.&lt;/li&gt;
&lt;li&gt;Lacks some additional tools like a cron parser or UUID generator.&lt;/li&gt;
&lt;li&gt;Need license (Start from 40$ to 80$)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DevToys
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5owrp0lx4f58ty2k4is3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5owrp0lx4f58ty2k4is3.png" alt="DevToys Screenshot" width="800" height="464"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free and open-source.&lt;/li&gt;
&lt;li&gt;Wide range of tools in a single application.&lt;/li&gt;
&lt;li&gt;Simple and user-friendly interface for Windows users.&lt;/li&gt;
&lt;li&gt;Includes unique tools like SQL Formatter and String Utilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not stable on MacOS and Linux&lt;/li&gt;
&lt;li&gt;Lacks some unique features like a cron parser or README helper.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Open Dev
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwr57o4wiucrrb5ef3at.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwr57o4wiucrrb5ef3at.png" alt="OpenDev Screenshot" width="800" height="443"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-platform support (macOS, Windows, Linux, web).&lt;/li&gt;
&lt;li&gt;Open-source with potential for community-driven development.&lt;/li&gt;
&lt;li&gt;Includes unique tools like the Cron Parser and README Helper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Newer to the market, so it may not have the same level of polish as DevUtils.&lt;/li&gt;
&lt;li&gt;Does not include some features available in other tools, like SQL formatting or Markdown to HTML conversion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Each of these tools has its strengths, depending on your specific needs and the platform you use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevUtils&lt;/strong&gt; is perfect for macOS developers who want a highly polished, native experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevToys&lt;/strong&gt; is ideal for Windows users looking for a free, comprehensive utility tool, especially with its SQL Formatter and other string utilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Dev&lt;/strong&gt; is a great option for developers who need a cross-platform toolset with some unique features not found in the other two.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, the best choice depends on your operating system, the features you need, and your preference for open-source versus proprietary tools. Explore each tool to see which one fits your workflow the best.&lt;/p&gt;

&lt;p&gt;Try Open Dev on &lt;a href="https://github.com/Jamalianpour/open-dev/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or check out DevUtils and DevToys to see how they can enhance your development environment.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>flutter</category>
      <category>csharp</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
