<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Thiago Gonzaga on Medium]]></title>
        <description><![CDATA[Stories by Thiago Gonzaga on Medium]]></description>
        <link>https://medium.com/@devops_thiago?source=rss-437038ced80d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*pMc4OTuRjYa7Js5qE0vj1w.jpeg</url>
            <title>Stories by Thiago Gonzaga on Medium</title>
            <link>https://medium.com/@devops_thiago?source=rss-437038ced80d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 17 May 2026 10:16:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@devops_thiago/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[RAG in Real World: How to Use Java and LangChain4j with Corporate Data]]></title>
            <link>https://arquivolivre.com.br/rag-in-real-world-how-to-use-java-and-langchain4j-with-corporate-data-b4b6d9691592?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/b4b6d9691592</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[rags]]></category>
            <category><![CDATA[langchain4j]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Fri, 21 Nov 2025 12:46:01 GMT</pubDate>
            <atom:updated>2025-11-21T12:46:01.395Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S7-veG3X6GZkiMeS4NXWwQ.png" /></figure><h3>Overview</h3><p>It’s undeniable that generative AI has exploded in scale, kicking off a race among big tech companies for leadership in this space. Naturally, companies of all sizes want to ride this new technological wave.</p><p>However, when we’re talking about corporate data, several critical challenges arise: compliance, information security, intellectual property, and other factors that make it unfeasible to send sensitive data to public LLM APIs.</p><p>On top of that, many organizations face another issue: valuable information is scattered across multiple platforms, often poorly structured, making it hard for teams to access it efficiently.</p><p>Ironically, LLMs themselves have essential limitations: studies show they can hallucinate in up to 15–20% of cases when they lack proper context. Token costs can make some solutions impossible to run at scale.</p><p>So the big question is: how can we use AI safely with corporate data, deliver reliable results, and at the same time reduce token usage and costs?</p><p>The answer is RAG (Retrieval-Augmented Generation), and in this article, I’ll show how to implement it in Java using LangChain4j.</p><h3>What is RAG (Retrieval-Augmented Generation)?</h3><p>RAG is a technique that extends an LLM’s ability to answer questions without retraining the model. Instead of relying only on the model’s built-in knowledge, RAG fetches external data — documents, databases, APIs — to provide more specific, accurate, and up-to-date answers, without the cost of training or fine-tuning.</p><p>A simple analogy: imagine a meeting where an expert needs to answer questions.<br>Without RAG, they depend only on their memory.<br>With RAG, they can review documents, spreadsheets, and systems during the meeting to provide more precise, evidence-based answers.</p><p>Why is RAG needed? As mentioned earlier, “pure” LLMs suffer from critical limitations:</p><ul><li><strong>Hallucinations</strong>: They make things up when they don’t know the answer</li><li><strong>Outdated data</strong>: Training is frozen in time (knowledge cutoff)</li><li><strong>Lack of specificity</strong>: They don’t know your internal corporate data</li><li><strong>No sources</strong>: It’s hard to trace where the information came from</li></ul><p>RAG makes LLMs much more trustworthy by connecting them to real, verifiable data.</p><p>How does it work in practice? The RAG flow happens in well-defined steps:</p><ol><li><strong>User question</strong><br>The user asks something like: “What is the company’s expense reimbursement policy?”</li><li><strong>Retrieval<br></strong>The system: <br>- Detects that it needs internal information<br>- Search corporate documents, wikis, and PDFs<br>- Uses semantic search (not just keyword search)</li><li><strong>Vectorization (Embeddings)</strong><br>The documents and the question are converted into mathematical representations (vectors).<br>This allows semantic similarity comparison between the question and the documents.<br>Example: “reimbursement” and “refund” are treated as similar.</li><li><strong>Context enrichment</strong><br>The most relevant snippets are selected and injected into the prompt sent to the LLM.</li><li><strong>Answer generation</strong><br>The LLM analyzes the question <strong>plus</strong> the retrieved context and generates an answer grounded in real data.</li></ol><h3>Benefits for companies</h3><p>With RAG, your organization can:</p><p>✅ <strong>Reduce operational costs</strong>: Fewer tokens consumed, no model retraining needed<br>✅ <strong>Keep information up to date</strong>: Update documents, not the model<br>✅ <strong>Have reliable, traceable answers</strong>: Each answer can reference the source<br>✅ <strong>Maintain complete control</strong>: Corporate data stays in your own infrastructure<br>✅ <strong>Integrate with legacy systems</strong>: Connect to existing databases and APIs</p><h3>When not to use RAG</h3><p>RAG is powerful, but it’s not a silver bullet. There are scenarios where other approaches are safer, cheaper, or simply more effective.</p><p><strong>For exact, auditable answers</strong><br>RAG is not a good fit when a wrong answer has serious consequences — for example, pricing rules, tax calculations, medical dosing, or regulatory reports. In these cases, the LLM should <em>not</em> be the source of truth.<br>Instead, call a deterministic API, rules engine, or database, and use the model only to explain the result in natural language if needed.</p><p><strong>When latency is critical</strong><br>RAG adds extra steps (retrieval + generation), which naturally increases latency. If your application needs near-instant responses (e.g., some trading, fraud scoring, or real-time user flows), a simpler model, aggressive caching, or a cache-augmented generation (CAG) approach can be more appropriate.</p><p><strong>When the data is highly structured</strong><br>If your data is already well-structured in a database and the question is essentially a query or aggregation (“total sales by region in the last 30 days”), a Text-to-SQL agent or direct SQL access is usually more precise and reliable than RAG over exported text.</p><p><strong>When you already have a strong system of record/system of truth</strong><br>If the answer already lives in a reliable system of record — a database, rules engine, ERP, or any other transactional system — you should read from that source directly instead of asking the model to guess. RAG makes much more sense for discovery, summarization, and knowledge navigation over unstructured content. If the underlying data is highly volatile or noisy (logs, transient events, temporary documents), keeping RAG indexes fresh can become expensive and operationally complex; in such cases, it’s usually better to first stabilize/structure the source data and apply RAG only to what actually represents long-lived knowledge.</p><h3>Why Java for Enterprise AI?</h3><p>Even though Python dominates generative AI tutorials and examples, the reality in enterprises is very different. According to TechRepublic, 90% of Fortune 500 companies use Java in their mission-critical systems.</p><p>And here’s the key point: AI needs to live where the data is. In corporate environments, that usually means:</p><ul><li>Integration with legacy systems (ERP, CRM, corporate databases)</li><li>Compatibility with existing Java infrastructure (Spring, Jakarta EE, microservices)</li><li>Strict requirements around security, performance, and maintainability</li><li>Development teams are already specialized in Java</li></ul><p>Rewriting the entire infrastructure in Python to add AI is neither viable nor desirable.</p><p>LangChain4j solves this gap by bringing RAG and LLM capabilities into the Java ecosystem, allowing you to integrate AI directly into your existing applications without disruptive architectural changes.</p><h3>Getting to Know LangChain4j</h3><p>LangChain4j is a Java library that dramatically simplifies integrating LLMs and RAG-like techniques into enterprise applications. It’s essentially the Java equivalent of the popular Python LangChain, but designed specifically for the Java ecosystem and conventions.</p><h3>Main features</h3><p>✅ <strong>Native Spring Boot integration</strong>: Configuration via properties and auto-configuration<br>✅ <strong>Multiple LLM providers</strong>: OpenAI, Gemini, Claude, local models via Ollama<br>✅ <strong>Enterprise-grade vector stores</strong>: Chroma, Pinecone, Elasticsearch, or custom implementations<br>✅ <strong>Full observability</strong>: Integration with OpenTelemetry and Micrometer<br>✅ <strong>Type safety</strong>: Strongly typed APIs, no “magic strings”</p><p>The library abstracts away the complexity of embeddings, vector stores, and LLMs so that you can focus on business logic rather than low-level details.</p><h3>Implementing RAG in Practice</h3><p>To demonstrate how to implement RAG with LangChain4j, I created a project that indexes the Java 25 documentation and supports natural language queries. The complete source code is available on <a href="https://github.com/devops-thiago/my-java-genie">GitHub</a> and can be used as a starting point for your own implementation. Let’s walk through the main components.</p><h3>1. Project Structure</h3><pre>src/main/java/com/javarag/<br>├── Application.java              # Spring Boot application entry point<br>├── config/                       # System configuration<br>│   ├── ModelConfig.java          # LLM and embeddings configuration<br>│   ├── VectorDbConfig.java       # Vector store configuration<br>│   ├── IngestionConfig.java      # Document ingestion configuration<br>│   └── QueryConfig.java          # Query configuration<br>├── service/                      # Core RAG services<br>│   ├── IngestionService.java     # Orchestrates document ingestion<br>│   ├── QueryService.java         # Orchestrates query processing<br>│   ├── RetrievalEngine.java      # Semantic search engine<br>│   ├── DocumentProcessor.java    # Interface for document chunking<br>│   └── RecursiveCharacterSplitter.java # Smart chunking implementation<br>├── repository/                   # Vector store access layer<br>│   └── VectorRepository.java     # Interface for vector operations<br>├── model/                        # Data models<br>│   ├── QueryResponse.java        # Query response<br>│   ├── DocumentChunk.java        # Document chunk<br>│   └── TokenUsageMetrics.java    # Token usage metrics<br>└── controller/                   # REST controllers<br>    └── ChatController.java       # Chat interaction API</pre><h3>2. System Configuration</h3><p>The first step is configuring the core components via application.properties:</p><pre># Language model configuration<br>rag.model.provider=openai<br>rag.model.name=gpt-4o-mini<br>rag.model.temperature=0.2<br>rag.model.max-tokens=2048<br><br># Embeddings configuration<br>rag.model.embedding.provider=openai<br>rag.model.embedding.model=text-embedding-3-small<br><br># Vector store configuration (Chroma)<br>rag.vector.provider=chroma<br>rag.vector.host=localhost<br>rag.vector.port=8000<br>rag.vector.collection=java-docs<br><br># Ingestion configuration<br>rag.ingestion.chunk-size=1000<br>rag.ingestion.chunk-overlap=100<br>rag.ingestion.batch-size=10<br><br># Query configuration<br>rag.query.max-retrieved-chunks=5<br>rag.query.similarity-threshold=0.75<br>rag.query.timeout-seconds=30</pre><p>The application follows standard Spring Boot configuration using @ConfigurationProperties classes:</p><pre>@ConfigurationProperties(prefix = &quot;rag.model&quot;)<br>public class ModelConfig {<br>    private String provider = &quot;openai&quot;;<br>    private String name = &quot;gpt-4o-mini&quot;;<br>    private Double temperature = 0.2;<br>    private Integer maxTokens = 2048;<br><br>    // getters and setters...<br>}</pre><h3>3. Document Ingestion</h3><p>Ingestion is the process of preparing documents for semantic search. Here’s the main service:</p><pre>@Service<br>public class IngestionService {<br><br>    private final DocumentLoader documentLoader;<br>    private final DocumentProcessor documentProcessor;<br>    private final EmbeddingModelProvider embeddingModel;<br>    private final VectorRepository vectorRepository;<br><br>    public IngestionResult ingestDocuments(Path documentPath) {<br>        logger.info(&quot;Starting document ingestion from path: {}&quot;, documentPath);<br><br>        // 1. Load documents from directory<br>        List&lt;Document&gt; documents = documentLoader.loadDocuments(documentPath);<br><br>        IngestionResult result = new IngestionResult();<br><br>        for (Document document : documents) {<br>            // 2. Process document (chunking)<br>            List&lt;DocumentChunk&gt; chunks = documentProcessor.processDocument(document);<br><br>            // 3. Process chunks in batches<br>            int batchSize = config.getBatchSize();<br>            for (int i = 0; i &lt; chunks.size(); i += batchSize) {<br>                List&lt;DocumentChunk&gt; batch = chunks.subList(<br>                    i,<br>                    Math.min(i + batchSize, chunks.size())<br>                );<br><br>                // 4. Generate embeddings for the batch<br>                List&lt;String&gt; texts = batch.stream()<br>                    .map(DocumentChunk::getContent)<br>                    .collect(Collectors.toList());<br><br>                List&lt;float[]&gt; embeddings = embeddingModel.embedBatch(texts);<br><br>                // 5. Store in vector store<br>                vectorRepository.storeBatch(batch, embeddings);<br>            }<br><br>            result.incrementDocumentsProcessed();<br>        }<br><br>        return result;<br>    }<br>}</pre><h4>Smart Chunking</h4><p>Chunking is crucial for RAG success. The project implements a recursive splitter that tries to break the text at natural boundaries:</p><pre>@Service<br>public class RecursiveCharacterSplitter implements DocumentProcessor {<br><br>    // Separators in order of preference<br>    private static final String[] SEPARATORS = {<br>        &quot;\n\n\n&quot;,  // Multiple paragraph breaks<br>        &quot;\n\n&quot;,    // Paragraph break<br>        &quot;\n&quot;,      // Line break<br>        &quot;. &quot;,      // End of sentence<br>        &quot;! &quot;,      // Exclamation<br>        &quot;? &quot;,      // Question<br>        &quot;; &quot;,      // Semicolon<br>        &quot;, &quot;,      // Comma<br>        &quot; &quot;,       // Space<br>        &quot;&quot;         // Character-level fallback<br>    };<br><br>    public List&lt;DocumentChunk&gt; chunkText(String text, DocumentMetadata metadata) {<br>        List&lt;String&gt; textChunks = splitText(<br>            text,<br>            config.getChunkSize(),<br>            config.getChunkOverlap()<br>        );<br><br>        List&lt;DocumentChunk&gt; chunks = new ArrayList&lt;&gt;();<br>        for (int i = 0; i &lt; textChunks.size(); i++) {<br>            // Create chunk-specific metadata<br>            DocumentMetadata chunkMetadata = new DocumentMetadata(<br>                metadata.getSourceFile(),<br>                metadata.getSection(),<br>                i  // chunk index<br>            );<br><br>            // Estimate token count (4 chars ≈ 1 token)<br>            int tokenCount = estimateTokenCount(textChunks.get(i));<br><br>            chunks.add(new DocumentChunk(textChunks.get(i), chunkMetadata, tokenCount));<br>        }<br><br>        return chunks;<br>    }<br>}</pre><h3>4. Retrieval Engine</h3><p>The RetrievalEngine is responsible for finding relevant document chunks for a given query:</p><pre>@Service<br>public class RetrievalEngine {<br><br>    public List&lt;DocumentChunk&gt; retrieveRelevantChunks(String query) {<br>        logger.debug(&quot;Retrieving relevant chunks for query: {}&quot;, query);<br><br>        // 1. Generate query embedding<br>        float[] queryEmbedding = embeddingModel.embed(query);<br>        logger.debug(&quot;Generated query embedding with {} dimensions&quot;,<br>                     queryEmbedding.length);<br><br>        // 2. Similarity search<br>        int topK = queryConfig.getMaxRetrievedChunks();<br>        double threshold = queryConfig.getSimilarityThreshold();<br><br>        List&lt;ScoredDocument&gt; scoredDocuments = vectorRepository<br>            .similaritySearch(queryEmbedding, topK, threshold);<br><br>        // 3. Filter and convert results<br>        List&lt;DocumentChunk&gt; relevantChunks = scoredDocuments.stream()<br>            .filter(doc -&gt; doc.getSimilarityScore() &gt;= threshold)<br>            .limit(queryConfig.getMaxRetrievedChunks())<br>            .map(ScoredDocument::getChunk)<br>            .collect(Collectors.toList());<br><br>        logger.info(&quot;Retrieved {} relevant chunks for query&quot;, relevantChunks.size());<br>        return relevantChunks;<br>    }<br>}</pre><h3>5. Query Processing</h3><p>QueryService orchestrates the full RAG flow:</p><pre>@Service<br>public class QueryService {<br><br>    public QueryResponse processQuery(String question) {<br>        logger.info(&quot;Processing query: {}&quot;, truncateForLog(question));<br>        long startTime = System.currentTimeMillis();<br><br>        try {<br>            // Step 1: Retrieve relevant chunks<br>            List&lt;DocumentChunk&gt; relevantChunks =<br>                retrievalEngine.retrieveRelevantChunks(question);<br><br>            if (relevantChunks.isEmpty()) {<br>                return createNoResultsResponse(question, startTime);<br>            }<br><br>            // Step 2: Build prompt with context<br>            String prompt = promptBuilder.buildPrompt(question, relevantChunks);<br><br>            // Step 3: Generate answer with LLM<br>            GenerationResponse generationResponse = generateWithTimeout(prompt);<br>            String answer = generationResponse.getText();<br><br>            // Step 4: Extract source references<br>            List&lt;SourceReference&gt; sources = extractSourceReferences(relevantChunks);<br><br>            // Step 5: Record token usage<br>            TokenUsageMetrics tokenMetrics = new TokenUsageMetrics(<br>                generationResponse.getPromptTokens(),<br>                generationResponse.getCompletionTokens(),<br>                generationResponse.getTotalTokens()<br>            );<br>            tokenTracker.recordTokenUsage(question, tokenMetrics);<br><br>            long responseTime = System.currentTimeMillis() - startTime;<br>            return new QueryResponse(answer, sources, tokenMetrics, responseTime);<br><br>        } catch (ModelTimeoutException | ModelInvocationException e) {<br>            // Specific handling for model errors<br>            logger.error(&quot;Model error processing query: {}&quot;, e.getMessage());<br>            throw e;<br>        } catch (Exception e) {<br>            logger.error(&quot;Unexpected error processing query&quot;, e);<br>            throw new RagSystemException(&quot;Failed to process query&quot;, e);<br>        }<br>    }<br>}</pre><h3>6. REST API for Integration</h3><p>The system exposes a clean REST API for integration:</p><pre>@RestController<br>@RequestMapping(&quot;/api/chat&quot;)<br>public class ChatController {<br><br>    @PostMapping(&quot;/query&quot;)<br>    public ResponseEntity&lt;ChatResponse&gt; query(@Valid @RequestBody ChatRequest request) {<br>        logger.info(&quot;Received chat query for session: {}&quot;, request.getSessionId());<br><br>        QueryResponse queryResponse = chatService.processMessage(<br>            request.getSessionId(),<br>            request.getMessage(),<br>            request.getWebSocketSessionId()<br>        );<br><br>        ChatResponse response = ChatResponse.fromQueryResponse(queryResponse);<br>        return ResponseEntity.ok(response);<br>    }<br><br>    @GetMapping(&quot;/history&quot;)<br>    public ResponseEntity&lt;List&lt;ChatMessage&gt;&gt; getHistory(@RequestParam String sessionId) {<br>        List&lt;ChatMessage&gt; history = chatService.getHistory(sessionId);<br>        return ResponseEntity.ok(history);<br>    }<br>}</pre><h3>Important Implementation Details</h3><h3>Observability and Monitoring</h3><p>The system includes full observability with OpenTelemetry to trace each step of the RAG process:</p><pre>// Automatic span instrumentation<br>Span span = tracer.spanBuilder(&quot;process-query&quot;).startSpan();<br>try (Scope scope = span.makeCurrent()) {<br>    span.setAttribute(&quot;query.text&quot;, truncateForLog(question));<br>    span.setAttribute(&quot;query.chunks_retrieved&quot;, relevantChunks.size());<br>    span.setAttribute(&quot;llm.tokens.total&quot;, generationResponse.getTotalTokens());<br><br>    // Processing logic...<br><br>    span.setStatus(StatusCode.OK);<br>}</pre><h3>Cost Control</h3><p>The project tracks token usage in real time, which is essential to control LLM costs:</p><pre>public class TokenUsageTracker {<br><br>    public void recordTokenUsage(String query, TokenUsageMetrics metrics) {<br>        logger.info(&quot;Query tokens - Prompt: {}, Completion: {}, Total: {}&quot;,<br>                   metrics.getPromptTokens(),<br>                   metrics.getCompletionTokens(),<br>                   metrics.getTotalTokens());<br><br>        // Record metrics for monitoring<br>        metricsService.recordTokenUsage(metrics);<br>    }<br>}</pre><h3>Robust Error Handling</h3><p>The system differentiates between error types and applies specific strategies:</p><pre>// Configurable timeout to avoid endless calls<br>private GenerationResponse generateWithTimeout(String prompt) {<br>    int timeoutSeconds = queryConfig.getTimeoutSeconds();<br><br>    CompletableFuture&lt;GenerationResponse&gt; future = CompletableFuture<br>        .supplyAsync(() -&gt; languageModel.generate(request));<br><br>    try {<br>        return future.get(timeoutSeconds, TimeUnit.SECONDS);<br>    } catch (TimeoutException e) {<br>        future.cancel(true);<br>        throw new ModelTimeoutException(&quot;Generation timed out&quot;, e);<br>    }<br>}</pre><h3>Testing the System</h3><p>To demonstrate RAG in action, let’s run a few queries against the Java 25 docs.</p><h3>Example 1: Query about Virtual Threads</h3><pre>curl -X POST http://localhost:8080/api/chat/query \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;sessionId&quot;: &quot;test-session&quot;,<br>    &quot;message&quot;: &quot;How do I use Virtual Threads in Java 25?&quot;<br>  }&#39;</pre><p><strong>Response:</strong></p><pre>{<br>  &quot;answer&quot;: &quot;In Java 25, you can use Virtual Threads via Thread.ofVirtual() or Executors.newVirtualThreadPerTaskExecutor(). Virtual Threads are lightweight threads that let you write thread-per-task style concurrent code without the usual resource costs. Basic example:\n\nThread.ofVirtual().start(() -&gt; {\n    // your logic here\n});\n\nFor many tasks, use:\nvar executor = Executors.newVirtualThreadPerTaskExecutor();&quot;,<br>  &quot;sources&quot;: [<br>    {<br>      &quot;filename&quot;: &quot;virtual-threads.md&quot;,<br>      &quot;section&quot;: &quot;Basic Usage&quot;,<br>      &quot;chunkIndex&quot;: 2<br>    }<br>  ],<br>  &quot;tokenUsage&quot;: {<br>    &quot;promptTokens&quot;: 1250,<br>    &quot;completionTokens&quot;: 85,<br>    &quot;totalTokens&quot;: 1335<br>  },<br>  &quot;responseTimeMs&quot;: 1847<br>}</pre><h3>Example 2: Query about Pattern Matching</h3><pre>curl -X POST http://localhost:8080/api/chat/query \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;sessionId&quot;: &quot;test-session&quot;,<br>    &quot;message&quot;: &quot;What’s new in pattern matching in Java?&quot;<br>  }&#39;</pre><p>The system returns specific information extracted from the documentation, with exact references to the sources.</p><h3>Lessons Learned and Best Practices</h3><h3>1. Choosing Chunk Size</h3><p>After some experimentation, I found that chunks of 1,000 characters with 100 characters of overlap gave the best balance between:</p><ul><li>Enough context for the LLM to understand the content</li><li>Good granularity for semantic search</li><li>Token cost control</li></ul><h3>2. Similarity Threshold</h3><p>A similarity threshold of 0.75 filtered out irrelevant results without being too strict.<br>Very high values (0.9+) dropped relevant answers; very low values (0.6-) introduced too much noise.</p><h3>3. Prompt Engineering</h3><p>The prompt needs careful structure. The final version included:</p><ul><li>An explicit instruction about the system’s role</li><li>Context extracted from documents</li><li>The user’s question</li><li>Instructions about answer format and source citation</li></ul><h3>4. Memory Management</h3><p>To avoid OutOfMemoryError with large document volumes:</p><ul><li>Use configurable batch processing</li><li>Explicitly release large objects</li><li>Monitor heap usage</li></ul><h3>5. Fallbacks and Graceful Degradation</h3><p>The system always tries to provide something useful:</p><ul><li>If no relevant documents are found, it explains this and suggests rephrasing</li><li>If the LLM fails, it returns an informative error</li><li>Configurable timeouts prevent endless requests</li></ul><h3>Production Considerations</h3><h3>Security</h3><p>For production, you should implement:</p><pre>@Component<br>public class SecurityConfig {<br><br>    // Authentication and authorization<br>    @Bean<br>    public SecurityFilterChain filterChain(HttpSecurity http) {<br>        return http<br>            .oauth2ResourceServer(oauth2 -&gt; oauth2.jwt(withDefaults()))<br>            .authorizeHttpRequests(authz -&gt; authz<br>                .requestMatchers(&quot;/api/chat/**&quot;).hasRole(&quot;USER&quot;)<br>                .anyRequest().authenticated())<br>            .build();<br>    }<br><br>    // Rate limiting<br>    @Bean<br>    public RateLimiter rateLimiter() {<br>        return RateLimiter.create(10.0); // 10 requests/second per user<br>    }<br>}</pre><h3>Performance and Scalability</h3><pre>@Configuration<br>@EnableAsync<br>public class AsyncConfig {<br><br>    // Thread pool for async processing<br>    @Bean<br>    public TaskExecutor ragTaskExecutor() {<br>        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();<br>        executor.setCorePoolSize(5);<br>        executor.setMaxPoolSize(20);<br>        executor.setQueueCapacity(100);<br>        executor.setThreadNamePrefix(&quot;rag-&quot;);<br>        return executor;<br>    }<br><br>    // Cache for recent embeddings<br>    @Bean<br>    public CacheManager cacheManager() {<br>        return new ConcurrentMapCacheManager(&quot;embeddings&quot;, &quot;queries&quot;);<br>    }<br>}ja</pre><h3>Monitoring</h3><p>Key metrics to track:</p><ul><li>Response time per query</li><li>Token usage over time</li><li>Success/error rate</li><li>Vector store latency</li><li>Memory usage during ingestion</li></ul><h3>Alternatives and Considerations</h3><h3>Vector Stores</h3><ul><li><strong>Chroma</strong> (used in the project): Great for prototyping and development</li><li><strong>Pinecone</strong>: Managed service, excellent performance, but the cost can be high</li><li><strong>Elasticsearch</strong>: If your company already uses ES, you can reuse the infrastructure</li><li><strong>PostgreSQL with pgvector</strong>: Natural fit if you already have relational data</li></ul><h3>Embedding Models</h3><ul><li><strong>OpenAI text-embedding-3-small</strong>: Good cost/benefit, 1536 dimensions</li><li><strong>OpenAI text-embedding-3-large</strong>: Higher quality, 3072 dimensions, more expensive</li><li><strong>Local models</strong>: sentence-transformers via Ollama for complete control</li></ul><h3>Chunking Strategies</h3><ul><li><strong>Recursive</strong> (implemented): Good for general text</li><li><strong>Semantic</strong>: Uses embeddings to choose smarter boundaries.</li><li><strong>Type-specific</strong>: PDFs, code, and Markdown may need different strategies</li></ul><h3>Next Steps</h3><p>To evolve the system:</p><ol><li><strong>Reranking</strong>: Add a reranker model (like BGE) to improve retrieval quality</li><li><strong>Hybrid embeddings</strong>: Combine semantic search with lexical search (BM25)</li><li><strong>Agentic RAG</strong>: Allow the system to decide when to fetch more information</li><li><strong>Multimodal</strong>: Add support for images, diagrams, and tables</li><li><strong>Feedback loop</strong>: Collect user feedback to improve continuously</li></ol><h3>Conclusion</h3><p>RAG with Java and LangChain4j offers a robust path for companies that want to leverage generative AI while keeping complete control over corporate data. The implementation I showed here delivers:</p><p>✅ <strong>Security</strong>: Data stays in your infrastructure<br>✅ <strong>Reliability</strong>: Answers grounded in verifiable documents<br>✅ <strong>Observability</strong>: Full tracing of the entire process<br>✅ <strong>Scalability</strong>: Spring Boot-based architecture<br>✅ <strong>Cost control</strong>: Detailed token usage monitoring</p><p>The combination of <strong>Java, LangChain4j, and RAG </strong>unlocks huge possibilities: from internal assistants that know your company policies to systems that can answer technical questions about your codebase.</p><p>The key is to start simple, measure everything, and evolve iteratively based on honest user feedback. With RAG, you’re not just “adding AI” — you’re building an intelligent system that grows together with your organization.</p><p>Source code: <a href="https://github.com/devops-thiago/my-java-genie">https://github.com/devops-thiago/my-java-genie</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b4b6d9691592" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/rag-in-real-world-how-to-use-java-and-langchain4j-with-corporate-data-b4b6d9691592">RAG in Real World: How to Use Java and LangChain4j with Corporate Data</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java 25 in Action: Real Features Solving Real Developer Pain]]></title>
            <link>https://arquivolivre.com.br/java-25-in-action-real-features-solving-real-developer-pain-5afe293bf390?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/5afe293bf390</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[java25]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Tue, 09 Sep 2025 01:17:48 GMT</pubDate>
            <atom:updated>2025-09-16T22:53:11.381Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Af--AgwAqlRX9xZ6DchskA.png" /></figure><p>Java 25 LTS is here. In this post, let&#39;s deep dive into what matters.</p><p>The most important thing to note: <strong>this is a Long-Term Support (LTS) version</strong>. That means stability and updates until at least 2033. A safe bet if you’re thinking about migration.</p><p>Of course, migration is never just a click away — especially if you’re still on Java 8 or 11. But here’s the deal: when new versions bring <strong>real performance gains</strong> and <strong>simpler code</strong>, the move starts to pay for itself.</p><p>Think about cloud costs. Memory consumption is money. Every GC improvement, every runtime optimization, every feature that reduces boilerplate… it all adds up. If your app runs fine and you don’t feel the pain, staying put is fine. But if you’re serious about cutting expenses or want your team to be more productive, Java 25 is worth a look.</p><p>👉 In this post, I’ll go through the features that matter most for developers. The goal isn’t just to show <em>what changed</em>, but to explain <strong>how these changes solve your daily pains</strong>.</p><h3>JEP-519 Compact Object Headers: Save Memory, Save Money</h3><p>Every object in Java has a <strong>header</strong> stored in the heap. This metadata is important for things like synchronization and garbage collection. The problem? It adds overhead.</p><p>Depending on the object, the header can take up to <strong>16 bytes</strong>. For small objects (say around 64 bytes), that’s about <strong>25% of the memory</strong> wasted just on the header.</p><p><strong>Java 25 changes that.</strong> With <em>Compact Object Headers</em>, the JVM can reduce headers to less than <strong>8 bytes</strong>, which means:</p><ul><li>Up to <strong>12% memory saved</strong> per object</li><li>Up to <strong>22% less heap usage</strong> overall</li><li>Fewer GC cycles → less CPU usage</li></ul><p>That’s not just theory — on large apps this can translate into <strong>real cloud cost savings</strong>.</p><p>🔧 To enable it, you need to start your JVM with:</p><pre>$ java -XX:+UseCompactObjectHeaders -jar yourapp.jar</pre><p>⚡ Bonus: frameworks like <strong>Spring</strong> and <strong>Quarkus</strong>, which rely heavily on object creation, can become much more resource-efficient with this improvement. Perfect for microservices running at scale.</p><p><em>💡 Note for beginners: Think of it like packing your suitcase tighter. Same stuff, less space. You don’t need to understand the JVM internals to benefit — just know that enabling this flag means your apps will use less memory and CPU.</em></p><h3>JEP-514/JEP-515 Ahead-of-Time Command-Line Ergonomics &amp; Method Profiling</h3><p>Startup time matters. Whether you’re deploying microservices, scaling pods in Kubernetes, or just restarting apps during development, those seconds (or minutes) add up.</p><p>Back in <strong>Java 24</strong>, we got <strong>Ahead-of-Time (AOT) Class Loading and Linking</strong>. It worked by analyzing your most-used classes, preloading them, and reducing startup time by up to <strong>42%</strong>. Pretty amazing… but there was a catch:</p><ul><li>You had to run the your app twice → first in <em>record </em>mode, then again in <em>create</em> cache mode</li><li>Great for advanced use cases (like generating caches across platforms)</li><li>But for most developers, it felt like <strong>overkill</strong></li></ul><p><strong>Java 25 fixes that.</strong> Thanks to <strong>command-line ergonomics</strong>, you can now simplify the process into <strong>one step</strong>:</p><pre># train and generate cache in a single command<br>$ java -XX:AOTCacheOutput=appcache.aot -jar your_app.jar<br><br># run your app using the created cache<br>$ java -XX:AOTCache=appcache.aot -jar your_app.jar</pre><p>No more two-step dance. One run, one cache.</p><p>But there’s more. <strong>Java 25 also introduces Ahead-of-Time Method Profiling.</strong> This means the JVM doesn’t just look at classes — it also tracks your <strong>most-used methods</strong> and puts them into the cache.</p><p>🚀 The result:</p><ul><li>Faster warmup times</li><li>Apps hit <strong>peak performance quicker</strong></li><li>Ideal for services that need to <strong>scale fast, recover fast, or deploy fast</strong></li></ul><h3>Reducing Boilerplate: Write Less, Do More</h3><p>One of the common complaints about Java has always been <em>boilerplate</em>. Too much ceremony just to run something simple. <strong>Java 25 takes a big step forward</strong> to make the language leaner and more fluent.</p><h4>1. JEP-512 Compact Source Files</h4><p>For quick scripts, demos, or small apps, you no longer need to wrap everything inside a class. You can just write a main method, define global variables, or add helper methods without static.</p><pre>final String HELLO_WORLD_FORMAT = &quot;Hello, %s&quot;;<br>String name;<br><br>void main(){<br>  name = &quot;Thiago&quot;;<br>  IO.println(output(name));<br>}<br><br>String output(String name) {<br>  return String.format(HELLO_WORLD_FORMAT, name);<br>}</pre><h4>2. JEP-513 Flexible Constructors</h4><p>Before Java 25, super() or this() had to be the <strong>first line</strong> of any constructor. That made input validation awkward. Now, you can place them anywhere in the constructor body.</p><pre>class User {<br>  private final String name;<br><br>  User(String name) {<br>    if (name == null || name.isBlank()) {<br>      throw new IllegalArgumentException(&quot;Name cannot be empty&quot;);<br>    }<br>    super(); // now allowed after validation<br>    this.name = name;<br>  }<br>}</pre><h4>3. JEP-511 Simpler Module Imports</h4><p><a href="https://openjdk.org/jeps/261">Modules</a> were introduced back in Java 9 to better organize dependencies. But the import syntax was verbose, often requiring multiple imports or wildcards.</p><p><strong>Java 25 streamlines this</strong> by allowing you to import the module itself, while still respecting requires, exports, and transitive rules.</p><pre>import module java.sql;<br><br>void main() {<br>  Connection conn = DriverManager.getConnection(&quot;url&quot;, &quot;user&quot;, &quot;password&quot;);<br>}</pre><p>Jav<strong>a 25 streamlines </strong>this by allowing you to import the module while respecting requires, exports, and transitive rules.</p><ul><li>For <strong>newbies and instructors</strong>, Java is now easier to teach and learn.</li><li>For <strong>senior devs</strong>, writing scripts, microservices, or demos becomes faster and less noisy.</li><li>Overall, Java is catching up with the simplicity you’d expect in modern languages — without losing its power.</li></ul><h3><strong>JEP-506 </strong>Scoped Values: A Safer Alternative to ThreadLocal</h3><p>Since the early 2000s, ThreadLocal has been the go-to tool for passing context like user IDs, tokens, or request metadata. But let’s be honest—it has also caused more than a few headaches:</p><ul><li>Hard-to-reason-about data flow</li><li>Risk of memory leaks if you forget remove()</li><li>Mutable values that can be replaced accidentally</li></ul><p><strong>Java 25 changes the game.</strong> With <strong>Scoped Values</strong>, you now have a safer and clearer way to share context.</p><pre>final ScopedValue&lt;String&gt; USER = ScopedValue.newInstance();<br><br>void main() {<br>  IO.println(&quot;Before: &quot; + user());<br>  ScopedValue.where(USER, &quot;Admin&quot;).run(() -&gt; {<br>    IO.println(&quot; User: &quot; + user());<br>    ScopedValue.where(USER, &quot;Guest&quot;).run(() -&gt; {<br>      IO.println(&quot; Inner scope user: &quot; + user());<br>    });<br>    IO.println(&quot; User: &quot; + user());<br>  });<br>  IO.println(&quot;After: &quot; + user());<br>}<br><br>String user() { return USER.isBound() ? USER.get() : &quot;unbound&quot;; }</pre><p>🔑 Scoped Values fix the ThreadLocal pain points:</p><ul><li><strong>Immutable once bound</strong> — no accidental mutation</li><li><strong>Bounded lifetime</strong> — values disappear automatically when the scope ends</li><li><strong>Clear scope structure</strong> — values are passed explicitly</li></ul><p>Even better: <strong>child virtual threads</strong> can inherit Scoped Values, but only when used with <strong>Structured Concurrency</strong> (JEP-505, still in preview). Regular threads do not inherit scoped values by default.</p><p>💡 ThreadLocal still has valid use cases (like caching expensive mutable objects), but for <strong>one-way, immutable data flow</strong>, <strong>ScopedValue is the future</strong>.</p><h3>Other Stable Features</h3><ul><li><strong>Generational Shenandoah (JEP-521):</strong> A new generational mode for the Shenandoah GC that improves efficiency by separating young and old objects.</li><li><strong>Key Derivation Function API (JEP-510):</strong> Standard cryptography API for secure key derivation (like turning passwords into encryption keys).</li><li><strong>JFR Cooperative Sampling (JEP-518):</strong> Reduces overhead when profiling applications with Java Flight Recorder by coordinating thread sampling.</li><li><strong>JFR Method Timing &amp; Tracing (JEP-520):</strong> Adds detailed method-level timing and tracing to JFR for deeper performance insights.</li></ul><h3>Preview, Incubator &amp; Experimental Features</h3><ul><li><strong>PEM Encodings of Cryptographic Objects (JEP-470, Preview):</strong> Makes it easier to read/write PEM-encoded keys and certificates.</li><li><strong>Stable Values (JEP-502, Preview):</strong> Immutable values designed for predictable behavior in concurrent programming.</li><li><strong>Structured Concurrency (JEP-505, Fifth Preview):</strong> Simplifies managing multiple tasks running in parallel.</li><li><strong>Primitive Types in Patterns, </strong><strong>instanceof, and </strong><strong>switch (JEP-507, Third Preview):</strong> Lets you use pattern matching directly with primitives.</li><li><strong>Vector API (JEP-508, Tenth Incubator):</strong> Enables high-performance vector computations for data-parallel workloads.</li><li><strong>JFR CPU-Time Profiling (JEP-509, Experimental):</strong> Profiles threads based on CPU time usage, not just elapsed wall time.</li></ul><h3>Wrapping It Up</h3><p>Java 25 is not just another release — it’s a <strong>Long-Term Support version</strong> that balances runtime optimizations with language simplicity.</p><ul><li><strong>Memory &amp; Performance:</strong> Compact Object Headers, AOT profiling, and new GC modes reduce heap usage, CPU cycles, and startup times.</li><li><strong>Developer Productivity:</strong> Compact source files, flexible constructors, and simpler module imports cut boilerplate and make Java easier to learn and teach.</li><li><strong>Safer Concurrency:</strong> Scoped Values offer a modern, immutable alternative to ThreadLocal, solving issues devs have faced for decades.</li><li><strong>Observability &amp; Security:</strong> JFR enhancements and new crypto APIs make apps easier to monitor and more secure.</li><li><strong>Cleaner Platform:</strong> By removing support for <strong>32-bit x86</strong>, Java 25 focuses on modern 64-bit architectures (Intel/AMD), simplifying future development.</li></ul><p>Migration may feel like a big leap if you’re still on Java 8 or 11. However, the <strong>performance gains, cost savings, and language improvements</strong> in Java 25 make it worth serious consideration.</p><p>🚀 Whether you care about cloud bills, developer velocity, or simply cleaner code, Java 25 has something to ease your daily pains.</p><p><strong>Reference</strong>: <a href="https://openjdk.org/projects/jdk/25/">https://openjdk.org/projects/jdk/25/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5afe293bf390" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/java-25-in-action-real-features-solving-real-developer-pain-5afe293bf390">Java 25 in Action: Real Features Solving Real Developer Pain</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TreeMap vs HashMap in Java — and When to Use Each (With a Real Interview Story)]]></title>
            <link>https://arquivolivre.com.br/treemap-vs-hashmap-in-java-and-when-to-use-each-with-a-real-interview-story-6d4af6f386e9?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/6d4af6f386e9</guid>
            <category><![CDATA[techinterviewtips]]></category>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Fri, 18 Apr 2025 21:39:33 GMT</pubDate>
            <atom:updated>2025-04-18T21:39:33.618Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*arfh4j3KTGmgiD8wzUUobA.png" /></figure><h3>TreeMap vs HashMap in Java — and When to Use Each (With a Real Interview Story)</h3><p>I’ve been helping a few developers lately — some of them recently went through interview processes. They nailed some parts, but something still didn’t move things forward.</p><p>Unfortunately, <strong>the hiring process isn’t under our control</strong>. Each company has its own approach, and sometimes even inside the same company, different candidates get different experiences. You can never fully predict what’s coming.</p><p>That’s why I always tell people: <strong>ask the right questions early on</strong>:</p><ul><li>What kind of technical interview should I expect?</li><li>Will it involve live coding?</li><li>What technologies are they focusing on?</li><li>Are there questions I should prepare for?</li></ul><p>Asking these early not only helps you prepare — it also helps you <strong>decide if the company aligns with your expectations</strong>. I’ve wasted time on long, exhausting interview processes that ended with offers I had to refuse. So my advice: protect your time, your energy, and ask early.</p><p>But this post isn’t just about interviews — it’s about <strong>storytelling</strong> and how you can answer interview questions even when they catch you off guard.</p><h3>My Story: “I Forgot… And That’s OK”</h3><p>Once, during a technical interview, I got asked a simple question:</p><blockquote><em>“Do you know the difference between a HashMap and a TreeMap?”<br>“When should you use one over the other?”<br>“Any performance differences? Big-O notation?”</em></blockquote><p>I froze for a second.</p><p>It’s not like I didn’t know the answer. I had learned it before. But… I hadn’t used it in a while.</p><p>So I answered honestly:</p><blockquote>“It’s been some time since I worked with that — I don’t remember exactly, but I’ll look it up right after this.”</blockquote><p>And I did. Straight to the docs.</p><p>Now I’ve done the work — and I’m sharing it here so <strong>you don’t have to dig around</strong> when it comes up in your interview.</p><h3>🆚 TreeMap vs HashMap in Java</h3><p>They both implement the Map&lt;K,V&gt; interface — but behave very differently.</p><h3>Key Differences:</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c3101d5e008221c37671d5433ca9ffb7/href">https://medium.com/media/c3101d5e008221c37671d5433ca9ffb7/href</a></iframe><h3>☝️ When to Use Which?</h3><h3>Use HashMap when:</h3><ul><li>You need speed — performance is critical.</li><li>Order doesn’t matter.</li><li>You’re doing lookups, caching, or counting things.</li></ul><h3>Use TreeMap when:</h3><ul><li>You need sorted keys.</li><li>You want to navigate ranges (e.g., min to max).</li><li>You care about iteration order.</li></ul><h3>🔎 Code Example — Order Matters</h3><pre>Map&lt;String, Integer&gt; hashMap = new HashMap&lt;&gt;();<br>hashMap.put(&quot;Orange&quot;, 1);<br>hashMap.put(&quot;Apple&quot;, 2);<br>hashMap.put(&quot;Banana&quot;, 3);<br>System.out.println(hashMap);<br>// Output might be: {Banana=3, Orange=1, Apple=2}</pre><pre>Map&lt;String, Integer&gt; treeMap = new TreeMap&lt;&gt;();<br>treeMap.put(&quot;Orange&quot;, 1);<br>treeMap.put(&quot;Apple&quot;, 2);<br>treeMap.put(&quot;Banana&quot;, 3);<br>System.out.println(treeMap);<br>// Output: {Apple=2, Banana=3, Orange=1}</pre><h3>⏱ Big-O Comparison</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0764db88a8c7f0da8a10df6d66a7f524/href">https://medium.com/media/0764db88a8c7f0da8a10df6d66a7f524/href</a></iframe><h3>🎯 Related Interview Questions</h3><ol><li><strong>What is LinkedHashMap?</strong><br>Like HashMap, but keeps <em>insertion order</em>. Great when you need predictable iteration.</li><li><strong>How does TreeMap sort keys?</strong><br>Internally uses a <strong>Red-Black Tree</strong> (self-balancing BST) to keep entries sorted.</li></ol><h3>✅ Wrap-Up</h3><p>So yeah — sometimes an interviewer will throw a curveball.<br>You might not use TreeMap every day. That’s fine.</p><p>But now that you’ve read this, you won’t just answer technically — you’ll <strong>tell a story</strong>, show your learning mindset, and explain things with confidence.</p><p><strong>Have you ever been asked this question in an interview?</strong><br>How did you answer it?</p><p>Let’s talk in the comments 👇</p><p>📬 Want more content like this — plus real stories about tech interviews, remote jobs, and DevOps?<br><strong>Join my newsletter:</strong><br>👉 <a href="https://sendfox.com/thiago">https://sendfox.com/thiago</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6d4af6f386e9" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/treemap-vs-hashmap-in-java-and-when-to-use-each-with-a-real-interview-story-6d4af6f386e9">TreeMap vs HashMap in Java — and When to Use Each (With a Real Interview Story)</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ What You Really Need to Know About Kubernetes in Interviews — Part I]]></title>
            <link>https://arquivolivre.com.br/what-you-really-need-to-know-about-kubernetes-in-interviews-part-i-1017dd97aa14?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/1017dd97aa14</guid>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[tech-career-advice]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[troubleshooting]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Tue, 08 Apr 2025 17:16:06 GMT</pubDate>
            <atom:updated>2025-04-08T17:16:47.856Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eiCNpL1QHwA0bItybDmjdw.png" /></figure><h3>🔧 What You <em>Really</em> Need to Know About Kubernetes in Interviews — Part I</h3><p>Recently, a friend of mine reached out — he was struggling with Kubernetes interview questions.</p><p>He’s been through several interviews already, and as it turns out, <strong>Kubernetes knowledge is </strong>the thing that keeps holding him back.</p><p>Kubernetes is showing up more and more in tech interviews — especially for roles in <strong>backend</strong>, <strong>DevOps</strong>, or anything <strong>cloud-related</strong>.</p><p>But what do you really need to know to avoid freezing mid-interview?</p><p>Let’s break down the <strong>essentials</strong> 👇</p><h3>🧪 Part I — Troubleshooting</h3><p>Most interviews want to know:</p><p><strong>Have you ever gotten into real trouble with Kubernetes — and do you know how to dig deep and find the root cause?</strong></p><p>That’s why <strong>troubleshooting is one of the most important skills</strong> for Kubernetes interviews.</p><p>Knowing where to start brings you much closer to your interview goal.</p><p>Let’s go through the key places to look when things go wrong.</p><h3>1️⃣ Start with kubectl get events 📋</h3><p>If something’s not right in the cluster, your first clue is almost always in the <strong>events</strong>.</p><p>Use this:</p><pre>kubectl get events -A</pre><p>This will give you a quick overview of what’s going wrong — like:</p><ul><li>❌ Pods in CrashLoopBackOff</li><li>🚨 Node memory pressure</li><li>📦 Image pull errors</li><li>📈 HPA scale activity</li><li>🔁 Deployments not rolling out</li></ul><p>This is your <strong>first stop</strong> when troubleshooting.</p><h3>2️⃣ Check the Logs 🔍</h3><p>If events don’t tell the full story, it’s time to dig deeper.</p><p>Run this to check logs in the kube-system namespace:</p><pre>kubectl logs -n kube-system &lt;pod-name&gt; -c &lt;container-name&gt;</pre><p>Here are the key components to know — and what kind of issues they might reveal:</p><h3>🌐 Ingress Controller</h3><p><strong>What it does</strong>: Manages external access (e.g., NGINX ingress).</p><p><strong>Common issue</strong>: Can’t access your app via browser? Check for route or TLS misconfig.</p><h3>🧠 Kube Controller Manager</h3><p><strong>What it does</strong>: Manages Deployments, ReplicaSets, scaling, etc.</p><p><strong>Common issue</strong>: No pods being created? This might have the answer.</p><h3>🧭 Kube Scheduler</h3><p><strong>What it does</strong>: Decides which node runs a pod.</p><p><strong>Common issue</strong>: Pod stuck in Pending? Maybe no node meets the requirements.</p><h3>🔗 Kube Proxy</h3><p><strong>What it does</strong>: Handles networking between services.</p><p><strong>Common issue</strong>: Can’t reach a ClusterIP service? Could be a proxy config issue.</p><h3>📡 CoreDNS</h3><p><strong>What it does</strong>: Resolves internal DNS for services.</p><p><strong>Common issue</strong>: Services can’t talk to each other by name? Look here.</p><h3>🧱 CNI Plugin (e.g., Calico)</h3><p><strong>What it does</strong>: Manages pod networking and policies.</p><p><strong>Common issue</strong>: Pods can’t reach each other across nodes? Might be a denied route or misconfigured policy.</p><h3>🧾Don’t Forget to Check Your App Logs</h3><p>Sometimes, the problem isn’t in the infrastructure — it’s inside your <strong>application</strong>.</p><p>Whether it’s a Spring Boot app crashing on startup or throwing 500s, your logs are usually the first place to look.</p><p>Use this to get the logs for your deployed app:</p><pre>kubectl logs &lt;pod-name&gt; -c &lt;container-name&gt; -n &lt;namespace&gt;</pre><h3>3️⃣ Use kubectl top to Check Resources 📊</h3><p>Sometimes, performance problems are just due to resource limits.</p><p>Check pod usage with:</p><pre>kubectl top pod &lt;pod-name&gt; -n &lt;namespace&gt;</pre><p>You’ll see how much CPU and memory your pods are using — super helpful when debugging autoscaling or performance issues.</p><h3>🧪 Pro tip: Set resource requests &amp; limits</h3><p>Defining requests and limits helps the scheduler do its job and keeps things stable.</p><pre>resources:<br>  requests:<br>    memory: &quot;256Mi&quot;<br>    cpu: &quot;250m&quot;<br>  limits:<br>    memory: &quot;512Mi&quot;<br>    cpu: &quot;500m&quot;</pre><p>This ensures your pods don’t hog or starve resources 💥</p><h3>💭 Final Thoughts</h3><p>Kubernetes in interviews doesn’t have to be scary.</p><p>Knowing <strong>where to look</strong>, how to <strong>read the signals</strong>, and how to <strong>check usage</strong> already puts you ahead of the pack.</p><h3>👇 Your Turn</h3><p>Have you ever faced a frustrating Kubernetes issue in production — or during an interview?</p><p>Leave a comment — I’d love to hear your story (and maybe turn it into a lesson for others too).</p><p>📬 Want more content like this — plus real stories about tech interviews, remote jobs, Java and DevOps?<br> <strong>Join my newsletter:</strong><br> 👉 <a href="https://sendfox.com/thiago">https://sendfox.com/thiago</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1017dd97aa14" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/what-you-really-need-to-know-about-kubernetes-in-interviews-part-i-1017dd97aa14">🔧 What You Really Need to Know About Kubernetes in Interviews — Part I</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ How to Use Stream.gather in Java 24 for More Powerful Stream Processing]]></title>
            <link>https://arquivolivre.com.br/introduction-e845992c4516?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/e845992c4516</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[java24]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Mon, 10 Mar 2025 20:11:44 GMT</pubDate>
            <atom:updated>2025-03-10T21:03:01.669Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J1mTItXdfzZ6aNH3hMDfHA.png" /></figure><p>While traveling to Belo Horizonte — the capital of Minas Gerais, Brazil — to catch The Offspring live (one of my all-time favorite bands, by the way), I found myself thinking about my next blog post. Just last week, I introduced some Java 24 changes to a large audience — even though Java 24 hasn’t officially launched yet (it’s set for March 18, 2025). That’s when I had an insight: <strong>developers can leverage </strong><strong>Stream.gather 🎯 to write cleaner, more efficient stream-based code—elevating their knowledge and staying ahead of the launch.</strong></p><p>One of the major highlights in Java 24, Stream.gather is a powerful feature that enhances stream processing by allowing custom intermediate operations. This article provides an easy-to-follow guide on how Stream.gather works, the problems it solves, and how to use it effectively.</p><p>If you want to start using Stream.gather today, you can either <strong>download the latest release candidate for JDK 24</strong> from <a href="https://jdk.java.net/24/">jdk.java.net/24</a> or <strong>enable preview features</strong> by passing --enable-preview to javac while compiling and java while executing with JDK 23.</p><h3><strong>🔍 How It Works</strong></h3><p>Stream.gather introduces <em>gatherers</em>—user-defined processors that transform stream elements in flexible ways. Unlike traditional intermediate operations, gatherers can perform:</p><ul><li>🔄 One-to-one transformations (like map)</li><li>📦 One-to-many, many-to-many, and many-to-one aggregations</li><li>🧠 Stateful transformations that track previous elements</li><li>⏳ Short-circuiting operations to transform infinite streams into finite ones</li></ul><p>A gatherer consists of four optional functions:</p><ul><li>🛠️ <strong>Initializer</strong> — Creates an intermediate state object (if needed).</li><li>🔗 <strong>Integrator</strong> — Processes incoming elements, possibly using intermediate state, and optionally produces output elements.</li><li>⚡ <strong>Combiner</strong> — Merges intermediate states into one.</li><li>🏁 <strong>Finisher</strong> — Finalizes processing by handling the final intermediate state and performing a final action at the end of the input stream.</li></ul><h3><strong>🤔 Which Problem It Can Solve</strong></h3><p>Java streams have been great for functional programming, but they lacked a way to define custom intermediate operations. Stream.gather fills this gap, making complex tasks like grouping, scanning, or stateful filtering more intuitive and efficient.</p><p>For example, imagine you have a list of students and want to split them into groups of three. Before Java 24, you would do it like this:</p><pre>import java.util.stream.IntStream;<br><br>public class BeforeJava24 {<br>    private record Student(String name) {}<br><br>    public static void main(String[] args) {<br>        var batchSize = 3;<br>        var students = IntStream.rangeClosed(1, 10)<br>                .mapToObj(i -&gt; new Student(String.format(&quot;Student #%d&quot;, i)))<br>                .toList();<br><br>        var groups = IntStream<br>                .range(0, students.size() % batchSize == 0 ? students.size() / batchSize : students.size() / batchSize + 1)<br>                .mapToObj(i -&gt; students.subList(i * batchSize, Math.min((i + 1) * batchSize, students.size())))<br>                .toList();<br><br>        groups.forEach(System.out::println);<br>    }<br>}</pre><p>With Java 24, you can achieve the same result more cleanly and efficiently using Stream.gather:</p><pre>import java.util.stream.Gatherers;<br>import java.util.stream.IntStream;<br><br>public class AfterJava24 {<br>    private record Student(String name) {}<br><br>    public static void main(String[] args) {<br>        var students = IntStream.rangeClosed(1, 10)<br>                .mapToObj(i -&gt; new Student(String.format(&quot;Student #%d&quot;, i)))<br>                .toList();<br><br>        var groups = students.stream()<br>                .gather(Gatherers.windowFixed(3))<br>                .toList();<br><br>        groups.forEach(System.out::println);<br>    }<br>}</pre><p>Notice how the code becomes cleaner and less complex with Stream.gather. 🚀</p><h3><strong>🛠️ Built-in Gatherers</strong></h3><p>Java 24 provides several ready-to-use gatherers in java.util.stream.Gatherers:</p><ul><li>📌 <strong>fold </strong>is a stateful many-to-one gatherer which performs an ordered reduction transformation.</li></ul><p>Before Java 24, you would probably do it as follows:</p><pre>import java.util.Arrays;<br><br>public class Fold {<br>    public static void main(String[] args) {<br>        var numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br>        var total = numbers.stream().mapToInt(Integer::intValue).sum();<br>        System.out.println(total);<br>    }<br>}</pre><p>Using fold in Java 24, the code looks like this:</p><pre>import java.util.Arrays;<br>import java.util.stream.Gatherers;<br><br>public class Fold {<br>    public static void main(String[] args) {<br>        var numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);<br>        // sums up all the integers in the list<br>        var total = numbers.stream()<br>                .gather(Gatherers.fold(() -&gt; 0, Integer::sum)) // starts at zero and sums the previous number with current one<br>                .findFirst().orElse(0);<br>        System.out.println(total);<br>    }<br>}</pre><p>Check in the example above — there was no need to use map since gather processes the stream pipeline and performs the sum in a single statement.</p><ul><li>⚙️ <strong>mapConcurrent</strong> is a stateful one-to-one gatherer which applies functions concurrently with a concurrency limit.</li></ul><p>In the example below, we have an implementation using Java 23:</p><pre>import java.net.URI;<br>import java.net.http.HttpClient;<br>import java.net.http.HttpRequest;<br>import java.net.http.HttpResponse;<br>import java.util.List;<br><br>public class MapConcurrent {<br>    public static void main(String[] args) {<br>        var urls = List.of(<br>                &quot;https://jsonplaceholder.typicode.com/todos/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/todos/2&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/posts/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/users/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/comments/1&quot;<br>        );<br><br>        try (HttpClient client = HttpClient.newHttpClient()) {<br>            urls.stream()<br>                    .map(url -&gt; client.sendAsync(<br>                                    HttpRequest.newBuilder().uri(URI.create(url)).build(),<br>                                    HttpResponse.BodyHandlers.ofString()<br>                            ).thenApply(HttpResponse::body)<br>                            .exceptionally(e -&gt; &quot;Error: &quot; + e.getMessage()))<br>                    .toList()<br>                    .forEach(response -&gt; System.out.println(response.join())); // Wait and print<br>        }<br>    }<br>}</pre><p>With Java 24, we can simplify this task using mapConcurrent:</p><pre>import java.io.IOException;<br>import java.net.URI;<br>import java.net.http.HttpClient;<br>import java.net.http.HttpRequest;<br>import java.net.http.HttpResponse;<br>import java.util.List;<br>import java.util.function.Function;<br>import java.util.stream.Gatherers;<br><br>public class MapConcurrent {<br>    public static void main(String[] args) {<br>        var urls = List.of(<br>                &quot;https://jsonplaceholder.typicode.com/todos/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/todos/2&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/posts/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/users/1&quot;,<br>                &quot;https://jsonplaceholder.typicode.com/comments/1&quot;<br>        );<br><br>        final Function&lt;String, String&gt; fetchData = url -&gt; {<br>            try (HttpClient client = HttpClient.newHttpClient()) {<br>                var resp = client.send(<br>                        HttpRequest.newBuilder().uri(URI.create(url)).build(),<br>                        HttpResponse.BodyHandlers.ofString());<br>                return resp.body();<br>            } catch (IOException | InterruptedException ex) {<br>                return &quot;&quot;;<br>            }<br>        };<br>        // fetches all the 5 url at once<br>        urls.stream().gather(Gatherers.mapConcurrent(urls.size(), fetchData))<br>                .toList()<br>                .forEach(System.out::println);<br>    }<br>}</pre><p>Look how the code is now simpler and easier to maintain.</p><ul><li>🔄 <strong>scan</strong> is a stateful one-to-one gatherer that applies a function using the current state and element to produce the next.</li></ul><p>Here’s how we handled this before Java 24 in an example where we calculate the interest of each installment in a financing:</p><pre>import java.util.stream.IntStream;<br><br>public class Scan {<br>    public static void main(String[] args) {<br>        double principal = 10000; // Loan Amount<br>        double annualInterestRate = 12; // 12% per year<br>        int numInstallments = 12; // 12 months<br><br>        // Convert annual interest rate to monthly interest rate<br>        double monthlyInterestRate = (annualInterestRate / 100) / 12;<br>        double emi = (principal * monthlyInterestRate * Math.pow(1 + monthlyInterestRate, numInstallments)) /<br>                (Math.pow(1 + monthlyInterestRate, numInstallments) - 1);<br><br>        System.out.println(&quot;Reducing Balance Installment Schedule:&quot;);<br><br>        // Mutable array to track outstanding balance<br>        double[] balance = {principal};<br><br>        IntStream.rangeClosed(1, numInstallments)<br>                .forEach(i -&gt; {<br>                    double interest = balance[0] * monthlyInterestRate;<br>                    double principalRepayment = emi - interest;<br>                    balance[0] -= principalRepayment;  // Reduce principal balance<br><br>                    System.out.printf(&quot;Month %d: Installment = %.2f, Interest = %.2f, Principal = %.2f, Remaining Balance = %.2f\n&quot;,<br>                            i, emi, interest, principalRepayment, balance[0]);<br>                });<br>    }<br>}</pre><p>And here’s how it looks in Java 24:</p><pre>import java.util.HashMap;<br>import java.util.Map;<br>import java.util.stream.Gatherers;<br>import java.util.stream.IntStream;<br><br>public class Scan {<br>    public static void main(String[] args) {<br>        final double principal = 10000; // Loan Amount<br>        final double annualInterestRate = 12; // 12% per year<br>        final int numInstallments = 12; // 12 months<br><br>        // Convert annual interest rate to monthly interest rate<br>        final double monthlyInterestRate = (annualInterestRate / 100) / 12;<br>        final double emi = (principal * monthlyInterestRate * Math.pow(1 + monthlyInterestRate, numInstallments)) /<br>                (Math.pow(1 + monthlyInterestRate, numInstallments) - 1);<br>        final double initialInterest = principal * monthlyInterestRate;<br>        <br>        System.out.println(&quot;Reducing Balance Installment Schedule:&quot;);<br><br>        IntStream.rangeClosed(1, numInstallments)<br>                .mapToObj(i -&gt; new HashMap&lt;String, Double&gt;(Map.of(&quot;Month&quot;, (double) i)))<br>                .gather(Gatherers.scan(() -&gt; Map.of(&quot;Balance&quot;, principal, &quot;Interest&quot;, initialInterest, &quot;PrincipalRepayment&quot;, emi - initialInterest),<br>                        (current, downstream) -&gt; {<br>                            double interest = current.get(&quot;Balance&quot;) * monthlyInterestRate;<br>                            double principalRepayment = emi - interest;<br>                            downstream.put(&quot;Balance&quot;, current.get(&quot;Balance&quot;) - principalRepayment);<br>                            downstream.put(&quot;Interest&quot;, interest);<br>                            downstream.put(&quot;PrincipalRepayment&quot;, emi - interest);<br>                            return downstream;<br>                        }))<br>                .forEach(entry -&gt; {<br>                    System.out.printf(&quot;Month %d: Installment = %.2f, Interest = %.2f, Principal = %.2f, Remaining Balance = %.2f\n&quot;,<br>                            entry.get(&quot;Month&quot;).intValue(), emi, entry.get(&quot;Interest&quot;), entry.get(&quot;PrincipalRepayment&quot;), entry.get(&quot;Balance&quot;));<br>                });<br>    }<br>}</pre><p>See that we no longer need an external mutable value to track the state — Gatherers.scan can maintain the state between iterations, allowing you to reuse the previous value to generate a new one. What do you think? The code is fancier now, isn&#39;t it? 😃</p><ul><li>📦 <strong>windowFixed </strong>groups elements into fixed-size lists.</li></ul><p>Imagine you need to process orders in batches of five orders each. To break them into groups, you would probably do something like this before Java 24:</p><pre>import java.util.stream.IntStream;<br><br><br>public class WindowFixed {<br>    record Order(int orderId) {}<br><br>    public static void main(String[] args) {<br>        final int batchSize = 5;<br>        var orders = IntStream.rangeClosed(1, 51)<br>                .mapToObj(Order::new)<br>                .toList();<br><br>        var batches = IntStream<br>                .range(0, orders.size() % batchSize == 0 ? orders.size() / batchSize : orders.size() / batchSize + 1)<br>                .mapToObj(i -&gt; orders.subList(i * batchSize, Math.min((i + 1) * batchSize, orders.size())))<br>                .toList();<br>        batches.forEach(System.out::println);<br>    }<br>}</pre><p>In Java 24, you can simply use windowFixed with the batch size, and it will look like this:</p><pre>import java.util.stream.Gatherers;<br>import java.util.stream.IntStream;<br><br>public class WindowFixed {<br>    record Order(int orderId) {}<br><br>    public static void main(String[] args) {<br>        final int batchSize = 5;<br>        var orders = IntStream.rangeClosed(1, 51)<br>                .mapToObj(Order::new)<br>                .toList();<br>        var batches = orders.stream()<br>                .gather(Gatherers.windowFixed(batchSize))<br>                .toList();<br>        batches.forEach(System.out::println);<br>    }<br>}</pre><p>Now it looks simpler and easier to read.</p><ul><li>🔍 <strong>windowSliding </strong>is<strong> </strong>similar to windowFixed, but with overlapping groups.</li></ul><p>Moving averages are a great example of where we can use a sliding window. Here&#39;s how it was done in Java prior to 24:</p><pre>import java.util.Arrays;<br>import java.util.stream.Collectors;<br>import java.util.stream.IntStream;<br><br>public class WindowSliding {<br>    public static void main(String[] args) {<br>        var data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);<br>        int windowSize = 3;<br>        var movingAverages = IntStream.rangeClosed(0, data.size() - windowSize)<br>                .mapToObj(i -&gt; data.subList(i, i + windowSize)<br>                        .stream()<br>                        .mapToDouble(Double::doubleValue)<br>                        .average()<br>                        .orElse(0.0))<br>                .collect(Collectors.toList());<br>        System.out.println(movingAverages);<br>    }<br>}</pre><p>With Java 24, we can use windowSliding to reduce the complexity of the code above:</p><pre>import java.util.Arrays;<br>import java.util.stream.Gatherers;<br><br>public class WindowSliding {<br>    public static void main(String[] args) {<br>        var data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);<br>        int windowSize = 3;<br>        var movingAverages = data.stream()<br>                .gather(Gatherers.windowSliding(windowSize))<br>                .gather(Gatherers.scan(() -&gt; 0.0,<br>                        (_, windows) -&gt; windows.stream().mapToDouble(Double::doubleValue)<br>                                .average().orElse(0.0)))<br>                .toList();<br>        System.out.println(movingAverages);<br>    }<br>}</pre><p>In the end, we see that we don&#39;t need to use map or collect to get the same results.</p><p><strong>⚡ Parallel Processing with Gatherers</strong></p><p>Parallel execution in Stream.gather operates in two modes:</p><ol><li><strong>Without a Combiner</strong> — The upstream and downstream run concurrently, similar to parallel().forEachOrdered().</li><li><strong>With a Combiner</strong> — Supports parallel reductions, akin to parallel().reduce().</li></ol><p>Example using a parallel gatherer to get the maximum prime number between 1 and 1000:</p><pre>import java.util.Objects;<br>import java.util.Optional;<br>import java.util.stream.Gatherer;<br>import java.util.stream.IntStream;<br><br>class LargestPrimeGatherer {<br><br>    public static void main(String[] args) {<br>        Optional&lt;Integer&gt; largestPrime = IntStream.rangeClosed(1, 10000)<br>                .boxed()<br>                .filter(LargestPrimeGatherer::isPrime)    // Filter only prime numbers<br>                .gather(selectOne(Math::max))             // Use custom Gatherer<br>                .parallel()<br>                .findFirst();                             // Extract the largest prime<br><br>        System.out.println(&quot;Largest prime number between 1 and 10,000: &quot; +<br>                largestPrime.orElse(-1)); // Print result<br>    }<br><br>    // Custom Gatherer to find the largest prime number<br>    static Gatherer&lt;Integer, ?, Integer&gt; selectOne(java.util.function.BinaryOperator&lt;Integer&gt; selector) {<br>        Objects.requireNonNull(selector, &quot;selector must not be null&quot;);<br><br>        // Private state to track information across elements<br>        class State {<br>            Integer value = null;  // The current best value<br>        }<br><br>        return Gatherer.of(<br>                State::new,  // The initializer creates a new State instance<br><br>                // The integrator<br>                Gatherer.Integrator.ofGreedy((state, element, downstream) -&gt; {<br>                    if (state.value == null) {<br>                        state.value = element;  // First value<br>                    } else {<br>                        state.value = selector.apply(state.value, element); // Compare and update max<br>                    }<br>                    return true;<br>                }),<br><br>                // The combiner, used during parallel evaluation<br>                (leftState, rightState) -&gt; {<br>                    if (leftState.value == null) return rightState;  // If left is empty, take right<br>                    if (rightState.value == null) return leftState;  // If right is empty, take left<br>                    leftState.value = selector.apply(leftState.value, rightState.value);  // Select max<br>                    return leftState;<br>                },<br><br>                // The finisher<br>                (state, downstream) -&gt; {<br>                    if (state.value != null)<br>                        downstream.push(state.value);  // Emit the selected value<br>                }<br>        );<br>    }<br><br>    // Prime checking function<br>    private static boolean isPrime(int num) {<br>        if (num &lt; 2) return false;<br>        return IntStream.rangeClosed(2, (int) Math.sqrt(num))<br>                .noneMatch(divisor -&gt; num % divisor == 0);<br>    }<br>}</pre><p>Notice that in this example, we have a more complex implementation where we define a Gatherer interface with an integrator, combiner, and finisher. We also implement a function that returns a gatherer and accepts an operator to select the maximum value. In this case, we use Math.max, which finds the maximum between two numbers.</p><h3><strong>🛠️ Creating Your Own Gatherer</strong></h3><p>Developers can define custom gatherers using Gatherer.ofSequential() or by implementing Gatherer directly, as shown in the example above using Gatherer.of(). Here&#39;s an example of a gatherer that emits distinct names based on their length. If two names have the same length, it picks only the first one that appears:</p><pre>import java.util.Arrays;<br>import java.util.HashSet;<br>import java.util.Set;<br>import java.util.stream.Collectors;<br>import java.util.stream.Gatherer;<br><br>public class Test {<br>    public static void main(String[] args) {<br>        Gatherer&lt;String, Set&lt;Integer&gt;, String&gt; distinctByLength = Gatherer.ofSequential(<br>                HashSet::new,<br>                (set, str, downstream) -&gt; {<br>                    // If this length is new, send &#39;str&#39; downstream<br>                    if (set.add(str.length())) {<br>                        downstream.push(str);<br>                    }<br>                    return true;<br>                },<br>                (set, downstream) -&gt; {}<br>        );<br><br>        var names = Arrays.asList(&quot;amanda&quot;, &quot;samantha&quot;, &quot;carolina&quot;, &quot;davis&quot;, &quot;john&quot;, &quot;juliana&quot;);<br><br>        names.stream()<br>                // &quot;gather&quot; is a proposed method in JEP 485 (not in standard Java yet)<br>                .gather(distinctByLength)<br>                .collect(Collectors.toSet())<br>                .forEach(System.out::println);<br>    }<br>}</pre><h3><strong>⚖️ Gather vs. Collect</strong></h3><p>While Collector is used for terminal aggregation, Gatherer is designed for intermediate transformations. Key differences:</p><ul><li><strong>🔄 Integrator vs. BiConsumer</strong> — Gatherers integrate state with a Downstream object.</li><li><strong>🏁 Finisher with Side Effects</strong> — Unlike Collector’s Function, Gatherer’s BiConsumer operates on Downstream.</li><li><strong>⏳ Supports Short-Circuiting</strong> — Gatherers can stop processing early, unlike most collectors.</li></ul><h3><strong>🎯 Conclusion</strong></h3><p>Java 24’s Stream.gather makes stream processing more powerful and expressive. Whether using built-in gatherers or creating custom ones, developers now have a tool that simplifies complex transformations while maintaining readability and efficiency. This feature represents a significant evolution in Java’s functional programming capabilities, bridging the gap between simplicity and flexibility. 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e845992c4516" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/introduction-e845992c4516">🚀 How to Use Stream.gather in Java 24 for More Powerful Stream Processing</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why You Should Consider Migrating to Java 24 ]]></title>
            <link>https://arquivolivre.com.br/why-you-should-consider-migrating-to-java-24-ec44c6c63cef?source=rss-437038ced80d------2</link>
            <guid isPermaLink="false">https://medium.com/p/ec44c6c63cef</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[java24]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[developer]]></category>
            <dc:creator><![CDATA[Thiago Gonzaga]]></dc:creator>
            <pubDate>Fri, 28 Feb 2025 02:26:46 GMT</pubDate>
            <atom:updated>2025-02-28T03:18:37.669Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9oAexg6hnaMYgdGlxxL_EA.jpeg" /></figure><p>This is my first blog post in <strong>many, many years</strong>, and I was wondering what to write about. I’m a <strong>community guy</strong> — I love sharing content! I’ve been part of the <strong>Java Noroeste JUG leadership</strong> for over a decade, and one thing I always enjoy is talking about the latest Java releases.</p><p>Back in <strong>September 2024</strong>, I gave a talk at a <strong>SouJava meetup</strong> about Java 23 features. So, I thought, why not do the same here but for <strong>Java 24</strong>? Let’s get straight to the point and talk about all the cool new things coming with <strong>Java 24</strong> and why you should be thinking about migrating!</p><h3>1️⃣ Virtual Threads Just Got Even Better! (JEP 491)</h3><p><strong>Virtual Threads (VTs)</strong> were a game-changer in <strong>Java 21</strong>, making concurrency easier and more scalable. But there was a problem: if you used <strong>synchronized methods</strong>, they could <strong>pin</strong> the carrier thread, blocking other tasks and hurting performance.</p><p>👉 <strong>What changed in Java 24?</strong><br>With <strong>JEP 491</strong>, synchronized methods no longer block the carrier thread! Now, virtual threads can <strong>park safely</strong>, making them much more efficient for applications that rely on traditional synchronization mechanisms.</p><p>💡 <strong>Example:</strong></p><p>Looking at the example below, pinning usually happens <strong>when a virtual thread is blocked inside a synchronized block for an extended period</strong>.</p><pre>import java.util.concurrent.*;<br><br>public class VirtualThreadPinningTest {<br>    private static final Object lock = new Object();<br><br>    static void blockingTask() {<br>        synchronized (lock) { // 🔴 This used to pin in Java 21<br>            System.out.println(Thread.currentThread() + &quot; - Holding lock...&quot;);<br>            try {<br>                Thread.sleep(3000); // Simulate a long blocking operation<br>            } catch (InterruptedException e) {<br>                e.printStackTrace();<br>            }<br>            System.out.println(Thread.currentThread() + &quot; - Released lock!&quot;);<br>        }<br>    }<br><br>    public static void main(String[] args) throws InterruptedException {<br>        var executor = Executors.newVirtualThreadPerTaskExecutor();<br><br>        long start = System.currentTimeMillis();<br><br>        executor.submit(VirtualThreadPinningTest::blockingTask);<br>        Thread.sleep(500); // Ensures the first thread locks first<br>        executor.submit(VirtualThreadPinningTest::blockingTask);<br><br>        executor.shutdown();<br>        executor.awaitTermination(5, TimeUnit.SECONDS);<br><br>        long elapsed = System.currentTimeMillis() - start;<br>        System.out.println(&quot;Total execution time: &quot; + elapsed + &quot;ms&quot;);<br>    }<br>}</pre><p>Let’s explore how things turn out with Java 21:</p><pre>$ javac --release 21 VirtualThreadPinningTest.java<br><br>$ java -Djdk.tracePinnedThreads=full VirtualThreadPinningTest<br>VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1 - Holding lock...<br>VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1 reason:MONITOR<br>    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)<br>    java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)<br>    java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:640)<br>    java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:817)<br>    java.base/java.lang.Thread.sleepNanos(Thread.java:494)<br>    java.base/java.lang.Thread.sleep(Thread.java:527)<br>    VirtualThreadPinningTest.blockingTask(Main.java:11) &lt;== monitors:1<br>    java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)<br>    java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)<br>    java.base/java.lang.VirtualThread.run(VirtualThread.java:329)<br>VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1 - Released lock!<br>VirtualThread[#25]/runnable@ForkJoinPool-1-worker-3 - Holding lock...<br>Total execution time: 5516ms</pre><p>If we do the same in Java 24, the thread is no longer pinned within a synchronized block:</p><pre>$javac --release 24 Main.java<br><br>$ java --enable-preview -Djdk.tracePinnedThreads=full VirtualThreadPinningTest<br>VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1 - Holding lock...<br>VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1 - Released lock!<br>VirtualThread[#30]/runnable@ForkJoinPool-1-worker-2 - Holding lock...<br>Total execution time: 5522ms</pre><p>Although this is a simple example, you’ll notice it seems slower. However, the thread is no longer pinned, allowing other threads to run freely. If you’re using <strong>virtual threads</strong>, no changes are needed — your existing code will now scale more efficiently.</p><h3>2️⃣ G1 GC Just Got Smarter (JEP 475)</h3><p>If you’re running Java apps in the <strong>cloud</strong>, you’re probably using <strong>G1 GC</strong>. Java 24 introduces a performance optimization called <strong>Late Barrier Expansion</strong>, which <strong>reduces JVM overhead</strong> by delaying the insertion of GC barriers <strong>until later in the compilation process</strong>.</p><p>👉 <strong>What does this mean?</strong></p><ul><li>Lower <strong>GC overhead</strong> ✅</li><li>Faster <strong>JVM performance</strong> ✅</li><li><strong>More efficient cloud deployments</strong> ✅</li></ul><p>It’s a <strong>low-level change</strong> that makes Java <strong>even better</strong> for <strong>high-performance and cloud-native applications</strong>.</p><h3>3️⃣ Faster Startup with Ahead-of-Time Class Loading (JEP 483)</h3><p>Have you ever felt your <strong>Spring Boot</strong> app takes too long to start? <strong>Java 24</strong> brings <strong>Ahead-of-Time (AOT) Class Loading &amp; Linking</strong>, which <strong>reduces startup time by up to 42%</strong> when the JVM is trained properly.</p><p>💡 <strong>Example:</strong></p><p>Let&#39; take this small Java program as an example, it the <strong>Stream API</strong>, which loads almost <strong>600 JDK classes</strong> when executed.</p><pre>import java.util.*;<br>import java.util.stream.*;<br><br>public class HelloStream {<br>    public static void main(String ... args) {<br>        long start = System.currentTimeMillis();<br>        var words = List.of(&quot;hello&quot;, &quot;fuzzy&quot;, &quot;world&quot;);<br>        var greeting = words.stream()<br>                .filter(w -&gt; !w.contains(&quot;z&quot;))<br>                .collect(Collectors.joining(&quot;, &quot;));<br>        System.out.println(greeting);  // Output: hello, world<br>        long elapsed = System.currentTimeMillis() - start;<br>        System.out.println(&quot;Total execution time: &quot; + elapsed + &quot;ms&quot;);<br><br>    }<br>}</pre><p>✅ First, compile the program:</p><pre>$ javac HelloStream.java</pre><p>✅ Run it once to observe normal startup time:</p><pre>$ java HelloStream<br>hello, world<br>Total execution time: 9ms</pre><p>✅ Then run the application <strong>in a training mode</strong> to let the JVM analyze and record its AOT configuration:</p><pre>$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \<br>     -cp . HelloStream           <br>hello, world<br>Total execution time: 25ms</pre><p>This will generate an <strong>AOT configuration file (</strong><strong>app.aotconf)</strong>, storing details about the required classes and methods.</p><p>✅ Now, use the recorded configuration to create an <strong>AOT cache</strong>:</p><pre>$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \<br>     -XX:AOTCache=app.aot -cp . HelloStream            <br>AOTCache creation is complete: app.aot</pre><p>This <strong>does not run the program</strong> but instead generates an optimized cache file (app.aot) that speeds up future executions.</p><p>✅ Now, run the program using the cache:</p><pre>$ java -XX:AOTCache=app.aot -cp . HelloStream<br>hello, world<br>Total execution time: 1ms</pre><p>This results in <strong>faster startup</strong> because classes are loaded <strong>instantly from the cache</strong> rather than being read, parsed, and linked at runtime.</p><p>To conclude, the JVM trains itself by learning which classes are used often and preloads them. This helps reduce startup time and improves performance.</p><p>👉 <strong>Great for:</strong></p><ul><li><strong>Spring Boot</strong> 🚀</li><li><strong>Micronaut, Quarkus</strong> ☁️</li><li><strong>Serverless applications</strong> ⚡</li></ul><p>Less waiting, <strong>more coding!</strong></p><h3>4️⃣ New Class-File API (JEP 484)</h3><p>If you’ve ever worked with <strong>bytecode manipulation</strong> using <strong>ASM</strong> or other low-level tools, you know how painful it can be. Java 24 introduces a <strong>standard Class-File API</strong>, making it much easier to <strong>read, write, and transform class files</strong> without low-level hacks.</p><p>💡 <strong>Example: Using the new Class-File API</strong></p><p>This example we will:<br>✔ Create a new <strong>HelloWorld </strong>class<br>✔ Generate the <strong>main()</strong> method that prints text<br>✔ Write the <strong>.class</strong> file that can be loaded and executed by the <strong>JVM</strong></p><pre>import java.lang.classfile.ClassFile;<br>import java.lang.constant.ClassDesc;<br>import java.lang.constant.MethodTypeDesc;<br>import java.nio.file.Path;<br><br>public class Main {<br>    public static void main(String[] args) throws Exception {<br>        Path classFilePath = Path.of(&quot;HelloWorld.class&quot;);<br>        ClassFile.of().buildTo(classFilePath, ClassDesc.of(&quot;HelloWorld&quot;), classBuilder -&gt; {<br>            // Set class metadata<br>            classBuilder<br>                    .withVersion(68, 0) // Java 24 = major version 68<br>                    .withSuperclass(ClassDesc.of(&quot;java.lang.Object&quot;)); // Superclass name<br><br>            // Add the &quot;main&quot; method<br>            classBuilder.withMethod(<br>                    &quot;main&quot;,<br>                    MethodTypeDesc.ofDescriptor(&quot;([Ljava/lang/String;)V&quot;), // void method with String[] parameter<br>                    ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, // public static<br>                    methodBuilder -&gt; methodBuilder.withCode(code -&gt; code // System.out.println(&quot;Hello, world from Java 24!&quot;)<br>                            .getstatic(ClassDesc.of(&quot;java.lang.System&quot;), &quot;out&quot;, ClassDesc.of(&quot;java.io.PrintStream&quot;))<br>                            .ldc(&quot;Hello, world from Java 24!&quot;)<br>                            .invokevirtual(ClassDesc.of(&quot;java.io.PrintStream&quot;), &quot;println&quot;, MethodTypeDesc.ofDescriptor(&quot;(Ljava/lang/String;)V&quot;))<br>                            .return_())<br>            );<br>        });<br>        System.out.println(&quot;Generated HelloWorld.class successfully!&quot;);<br>    }<br>}</pre><p>✅ <strong>Compile with Java 24</strong></p><pre>javac Main.java</pre><p>✅ <strong>Run &amp; Verify the Bytecode</strong></p><pre>$ java Main                   <br>Generated HelloWorld.class successfully!<br><br>$ java HelloWorld             <br>Hello, world from Java 24!</pre><p>If you’re building <strong>instrumentation tools, agents, or compilers</strong>, this is a <strong>huge win</strong>!</p><h3>5️⃣ Stream.gather() — More Powerful Stream Transformations (JEP 485)</h3><p>Java Streams just got a <strong>new intermediate operation</strong>: Stream.gather(). It allows <strong>more flexible</strong> transformations, including:<br>✅ <strong>One-to-one</strong><br>✅ <strong>One-to-many</strong><br>✅ <strong>Many-to-many</strong></p><p>💡 <strong>Example: Before vs. After</strong></p><p>Before Java 24, you had to <strong>flatten streams manually</strong>:</p><p>In this example we want to convert an array like [1, 2, 3, 4, 5, 6, 7] into [[1, 2, 3], [4, 5, 6], [7]].</p><pre>import java.util.Arrays;<br>import java.util.List;<br>import java.util.stream.Stream;<br><br>public class Main {<br>    public static void main(String[] args) {<br>        // Collect all elements into a list first<br>        List&lt;Integer&gt; allNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);<br>        int batchSize = 3;<br><br>        // Generate batches from the list<br>        Stream&lt;List&lt;Integer&gt;&gt; batches = Stream.iterate(0, i -&gt; i &lt; allNumbers.size(), i -&gt; i + batchSize)<br>                .map(i -&gt; allNumbers.subList(i, Math.min(i + batchSize, allNumbers.size())));<br><br>        System.out.println(batches.toList());<br>    }<br>}</pre><p>With Stream.gather(), it’s much <strong>simpler</strong> and <strong>more efficient</strong>:</p><pre>import java.util.stream.Gatherers;<br>import java.util.stream.Stream;<br><br>public class Main {<br>    public static void main(String[] args) {<br>        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7).gather(Gatherers.windowFixed(3)).toList());<br>    }<br>}</pre><p>Output for both should be an array of arrays divided in blocks of 3 items:</p><pre>$ javac Main.java<br>$ java Main<br>[[1, 2, 3], [4, 5, 6], [7]]</pre><p>This makes working with <strong>nested or complex transformations</strong> a lot easier!</p><h3>6️⃣ JDK Linking Without JMODs (JEP 493)</h3><p>The JDK is getting smaller! With the new --enable-linkable-runtime option, you can build a JDK that allows jlink to create runtime images without JMOD files. This enables it to link JDK modules directly from the containing run-time image, resulting in a run-time image that is about 60% smaller than a full JDK run-time image.</p><p>✅ <strong>Same modules</strong><br>✅ <strong>Smaller footprint</strong><br>✅ <strong>Faster deployments</strong></p><p>If you’re shipping <strong>custom JDK run-time image</strong>, this is <strong>great news</strong>!</p><h3>7️⃣ Quantum-Resistant Cryptography (JEP 496 &amp; 497)</h3><p>With <strong>quantum computing</strong> advancing, traditional cryptographic algorithms like <strong>RSA and ECC</strong> could become vulnerable. Java 24 introduces <strong>post-quantum security</strong> with:</p><p>🔐 <strong>ML-KEM (Kyber)</strong> — For <strong>key exchange</strong><br>✍ <strong>ML-DSA (Dilithium)</strong> — For <strong>digital signatures</strong></p><p>This is <strong>huge</strong> for securing <strong>future-proof applications</strong>!</p><h3>🔍 What Else Is Changing in Java 24?</h3><h4>🆕 New Features &amp; Enhancements (Preview &amp; Experimental)</h4><p>Java 24 introduces several experimental and preview features that push the platform forward:</p><p><strong>🚀 Performance &amp; Memory Improvements</strong></p><ul><li><strong>JEP 404: Generational Shenandoah (Experimental)</strong> — Adds a generational mode to the Shenandoah garbage collector for better memory efficiency.</li><li><strong>JEP 450: Compact Object Headers (Experimental)</strong> — Reduces object header size to improve memory footprint.</li></ul><p><strong>🔐 Security &amp; Cryptography</strong></p><ul><li><strong>JEP 478: Key Derivation Function API (Preview)</strong> — Introduces a standardized API for key derivation functions to enhance cryptographic security.</li></ul><p><strong>🧵 Concurrency &amp; Scoped Values</strong></p><ul><li><strong>JEP 487: Scoped Values (Fourth Preview)</strong> — Improves efficiency over thread-local variables by providing immutable, inheritable values within a scope.</li><li><strong>JEP 499: Structured Concurrency (Fourth Preview)</strong> — Simplifies concurrent programming by treating related tasks as a single unit.</li></ul><p><strong>🖥️ Language &amp; Syntax Enhancements</strong></p><ul><li><strong>JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)</strong> — Enhances pattern matching to support primitive types.</li><li><strong>JEP 494: Module Import Declarations (Second Preview)</strong> — Simplifies module imports with new syntax.</li><li><strong>JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)</strong> — Allows instance mainmethods and streamlines single-file execution.</li></ul><p><strong>⏩ Performance-Oriented APIs</strong></p><ul><li><strong>JEP 489: Vector API (Ninth Incubator)</strong> — Continues enhancing SIMD vector operations for better CPU utilization.</li><li><strong>JEP 492: Flexible Constructor Bodies (Third Preview)</strong> — Adds more flexibility in constructor execution.</li></ul><h4>🗑️ Deprecations, Removals &amp; Restrictions</h4><p>Java 24 also phases out outdated features:</p><p><strong>📌 JNI &amp; Unsafe Restrictions</strong></p><ul><li><strong>JEP 472: Prepare to Restrict the Use of JNI</strong> — Lays groundwork for restricting Java Native Interface (JNI) in future releases.</li><li><strong>JEP 498: Warn upon Use of Memory-Access Methods in </strong><strong>sun.misc.Unsafe</strong> – Warns developers about unsafe memory operations.</li></ul><p><strong>🪦 Feature &amp; Platform Removals</strong></p><ul><li><strong>JEP 479: Remove the Windows 32-bit x86 Port</strong> — Ends support for 32-bit Windows.</li><li><strong>JEP 501: Deprecate the 32-bit x86 Port for Removal</strong> — Signals the eventual removal of 32-bit x86 support.</li><li><strong>JEP 486: Permanently Disable the Security Manager</strong> — Final removal of Java’s outdated Security Manager.</li><li><strong>JEP 490: ZGC: Remove the Non-Generational Mode</strong> — Fully transitions ZGC to a generational model.</li></ul><h3>Final Thoughts</h3><p>Java 24 brings <strong>major improvements</strong> in performance, scalability, and security. Whether you’re running <strong>cloud apps, microservices, or high-performance applications</strong>, there’s something valuable here.</p><p>So… are you upgrading to <strong>Java 24</strong>? Let me know! 🚀</p><h3>Happy coding! 💻</h3><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec44c6c63cef" width="1" height="1" alt=""><hr><p><a href="https://arquivolivre.com.br/why-you-should-consider-migrating-to-java-24-ec44c6c63cef">Why You Should Consider Migrating to Java 24 🚀</a> was originally published in <a href="https://arquivolivre.com.br">Arquivo Livre</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>