🔥 Day 12 — Stream vs Parallel Stream Java gives us stream() and parallelStream(), but using both interchangeably is a common performance trap. Here’s a concise, architecture-focused breakdown 👇 ✅ When stream() (sequential) is the right choice Use it by default unless there is a clear reason not to. ✔ Order matters ✔ Small dataset ✔ Computation is lightweight ✔ Tasks depend on external state ✔ Running inside a web request thread (avoid blocking!) Sequential streams = predictable, cheap, safe. 🚀 When parallelStream() actually helps Parallel streams shine only in specific scenarios: ✔ CPU-heavy operations ✔ Very large collections ✔ Pure functions (no shared mutable state) ✔ Independent tasks ✔ Running on multi-core servers ✔ Safe to use fork-join pool (or overridable) Example workloads: image processing, bulk calculations, data transformation. Rule: Only use parallel streams for CPU-bound operations on big datasets. ⚠️ When to AVOID parallelStream() Parallel is not always faster — sometimes it’s worse. ❌ Small collections (overhead > benefit) ❌ IO tasks (network/db calls block threads) ❌ Code modifying shared variables ❌ Inside web servers (uses common ForkJoinPool → thread starvation) ❌ Any scenario where ordering is important Parallel streams can cause unexpected latency spikes in prod if used blindly. 🧠 Architect’s Take: Parallel streams are powerful — but they borrow threads from the common ForkJoinPool, which your entire application also uses. One wrong usage in production can slow down every request. Default to sequential. Use parallel only when data and computation justify it. #100DaysOfJavaArchitecture #Java #Streams #Concurrency #SoftwareArchitecture #Microservices
Java Stream vs Parallel Stream: Performance Considerations
More Relevant Posts
-
🚀 Java Streams: Sequential vs Parallel — When to use what? A simple concept, but often misunderstood 👇 🔹 Sequential Stream → Runs on a single thread (one CPU core) → Processes data step-by-step → Lower overhead → Best for: small datasets, simple operations 🔹 Parallel Stream → Uses multiple threads (ForkJoinPool) → Splits data across multiple CPU cores → Processes tasks concurrently → Best for: large datasets, CPU-intensive operations 💡 Key Insight: Parallel streams are NOT always faster. ⚠️ They introduce: - Thread management overhead - Context switching cost - Possible issues with shared mutable state ✔️ Use Parallel Stream when: - Data size is large - Task is CPU-bound - Operations are stateless & independent ❌ Avoid when: - Small datasets - I/O operations (DB calls, API calls) - Order matters strictly 💼 Real-world example: In one of my use cases, processing large collections (like aggregations/search results) using parallel streams improved performance — but only after ensuring operations were stateless and thread-safe. ⚡ Pro Tip: Always benchmark before switching to parallel — assumptions can be misleading. #Java #StreamAPI #Java8 #Performance #Backend #SoftwareEngineerin
To view or add a comment, sign in
-
-
The "ThreadLocal" Trap: Why your Session Logic Fails in Vert.x and Kafka Transitioning from traditional synchronous Java development to an asynchronous, event-driven architecture with Vert.x and Apache Kafka is a rewarding journey, but it comes with a major wake-up call: Your traditional session mechanisms are probably obsolete. After a deep dive into development and debugging today, I’ve consolidated a few critical architectural shifts that every team must consider before writing the first line of code. 1. The Death of the Context-Thread Bond In a classic Servlet-based world, we rely heavily on ThreadLocal to store user sessions, security contexts, or trace IDs. It’s easy: one thread per request. In Vert.x, the Event Loop is king. A single request may jump across multiple threads, or a single thread may handle thousands of interleaved requests. The moment you hit an await or a Kafka send, your ThreadLocal context vanishes. 2. Statelessness is Not Optional In a Kafka-driven processor, the "session" doesn't exist in memory. If your processor needs to call a long-running remote API (like a heavy PDF parser), you cannot simply "wait" and expect the environment to stay the same. You must explicitly pass state through Message Headers or Metadata Objects. 3. Rethinking the "Long-Running" Task Synchronous systems "block." Asynchronous systems "flow." If a task takes 30 seconds, a traditional system hangs a thread. In an event-driven system, you should be looking at: Asynchronous Callbacks: Trigger the task and let the result flow back into a different Kafka topic. Context Propagation: Explicitly carrying userId and traceId within the payload metadata. 🔑 Key Takeaway for Architects: Don't try to retrofit synchronous patterns into an asynchronous world. If you don't design your context propagation strategy (how session data travels across the event bus or Kafka topics) during the blueprint phase, you will spend weeks debugging NullPointerExceptions and lost sessions. Design for the flow, not for the thread. 🦑 #Java #Vertx #ApacheKafka #SoftwareArchitecture #BackendDevelopment #Microservices #AsyncProgramming
To view or add a comment, sign in
-
Just shipped Phase 6 of my distributed messaging project — a C++ port of the log storage engine, rebuilt at bare metal. The Java implementation (Phase 5) used FileChannel scatter-gather writes: one syscall per append, p50 at 4,432 ns after eliminating GC pauses with off-heap MemorySegment slabs. The question was simple: what's the irreducible cost once you remove the JVM entirely? Result: 16.1 ns per 64-byte append. 3.70 GB/s throughput. That's ~275× faster at p50. Not because Java is slow — Phase 5 Java was already allocation-free on the hot path. The difference is the I/O model. FileChannel crosses the kernel boundary on every write. mmap doesn't. The CPU never leaves userspace. perf stat confirmed it: 68% backend-bound. The bottleneck is store bandwidth to L1D — the irreducible cost of sequential writes. No algorithmic waste to remove. valgrind --tool=massif confirmed zero heap allocation across 1,048,576 appends. The heap is flat from startup to shutdown. What's under the hood: - Lock-free SPSC ring buffer with acquire/release ordering, cache-line-aligned mmap-backed log segments with madvise(MADV_HUGEPAGE) for transparent huge pages - Directory-scanning LogManager with power-of-2 index — zero syscalls on the hot path - Compile-time hardware contracts via C++23 concepts (FitsCacheLine, IsHugePageAligned, IsPowerOfTwo) - Factory pattern via std::expected — no exceptions, no heap on the error path - 18 tests passing, Google Benchmark + Valgrind massif All design decisions documented as ADRs. Code on GitHub. → https://lnkd.in/gifTNMSB #LowLatency #CPlusPlus #HFT #SystemsProgramming #DistributedSystems #SoftwareEngineering #MemoryMappedIO #PerformanceEngineering
To view or add a comment, sign in
-
Spring Boot Logging – What really happens when INFO is disabled? In production, we usually turn off INFO logs. It feels like those log statements are completely ignored — but that’s not always true. The key thing to understand is this: Java evaluates the log statement before the logging framework decides to ignore it. Take this example: log.info("User data: " + user); Here, the string concatenation happens first. That means user.toString() is executed and a new String object is created. Only after that, the logging framework checks the log level and ignores it. So even though nothing is printed, you still created objects, used memory, and added work for the Garbage Collector. Over time, this leads to more GC activity and unnecessary CPU usage. Now compare it with: log.info("User data: {}", user); This looks similar, but behaves very differently. In this case, the logging framework checks whether INFO is enabled before formatting the message. If INFO is disabled, it simply skips everything — no string creation, no extra objects, no GC work, and minimal CPU usage. There’s one more subtle case: log.info("User data: {}", getUser()); Even though you’re using {}, the method getUser() is still executed before the log call. So the object gets created anyway, consuming CPU and again adding pressure on the Garbage Collector — even when the log is disabled. To truly avoid unnecessary work, especially for expensive operations, you need to guard it: if (log.isInfoEnabled()) { log.info("User data: {}", getUser()); } Now, the method runs only when INFO logging is actually enabled, avoiding wasted computation, object creation, GC cycles, and CPU overhead. The takeaway is simple: Logging frameworks can skip formatting, but they cannot stop Java from executing expressions. Writing log statements the right way can save memory, reduce GC pressure, and improve CPU efficiency — especially in high-scale applications. #Java #SpringBoot #Logging #Performance #GarbageCollection #CleanCode
To view or add a comment, sign in
-
-
Thread Pools to Virtual Threads. 🧵 OutOfMemoryError it is a nightmare that has always haunted Java developers. We're always been stuck with Platform Threads (heavyweight wrappers around OS threads). Since each one cost about 1MB of memory, handling 10,000 concurrent requests meant you either needed a massive, expensive server or had to write complex reactive code that nobody actually wants to debug. Enter Project Loom (Java 21+). I’ve been diving into Virtual Threads, and the "blocking" game has completely changed. Here’s why this matters for the modern backend: Cheap as Chips: Virtual threads are managed by the JVM, not the OS. They only cost a few hundred bytes. You can literally spawn one million threads on a standard laptop without breaking a sweat. The "Thread-per-Request" Revival: We can go back to writing simple, readable, synchronous code. No more "Callback Hell" or complex Mono/Flux chains just to keep the CPU busy while waiting for a database response. Massive Throughput: In I/O-heavy applications (which most Spring Boot apps are), Virtual Threads allow the CPU to switch to other tasks instantly while one thread waits for a slow API or SQL query. How to use it in Spring Boot 3.2+? It’s literally one line in your application.properties: spring.threads.virtual.enabled=true By flipping this switch, Tomcat/Undertow starts using Virtual Threads to handle web requests. It’s a complete paradigm shift that lets us build more scalable systems with less infrastructure cost. The takeaway for teams: We no longer have to choose between "easy-to-read code" and "high-performance code." With Java 21, we get both. #Java #SpringBoot #BackendDevelopment #ProjectLoom #SoftwareEngineering #Scalability #JVM
To view or add a comment, sign in
-
-
🦾 The Power of ForkJoin in Java When dealing with massive datasets or computationally heavy tasks, sequential processing is often the bottleneck. That’s where the ForkJoin Framework shines, implementing a "Divide and Conquer" strategy at the hardware level. Here is how it overcomes common parallelism challenges: 1. Efficient Resource Allocation (Work-Stealing) This is the "secret sauce." In a typical thread pool, if one thread finishes its tasks, it sits idle while others might be overwhelmed. In a ForkJoinPool, idle threads "steal" work from the back of the deques of busy threads. This ensures all CPU cores are consistently utilized. 2. Solving the "Divide and Conquer" Complexity Managing recursion and thread synchronization manually is error-prone. ForkJoin provides a structured way to: Fork: Split a large task into smaller, independent sub-tasks. Join: Wait for the sub-tasks to finish and combine their results. 3. Lightweight Task Management Unlike standard OS threads, ForkJoin tasks (like RecursiveTask or RecursiveAction) are extremely lightweight. You can run millions of these tasks within a much smaller pool of actual worker threads without the overhead of context switching. When should you use it? Recursive Problems: Like sorting large arrays (Parallel Sort) or processing complex tree structures. CPU-Intensive Work: When you have a lot of data and enough cores to handle it in parallel. Large Collections: When a simple for loop is no longer meeting your SLA. Pro-tip: For most everyday tasks, Java's parallelStream() uses a common ForkJoinPool under the hood. However, for specialized heavy-lifting, creating your own ForkJoinPool gives you much finer control over parallelism levels. #Java #Multithreading #ParallelComputing #Backend #SoftwareEngineering #Performance #Concurrency
To view or add a comment, sign in
-
-
Most Java performance issues don’t show up in code reviews They show up in object lifetimes. Two pieces of code can look identical: same logic same complexity same output But behave completely differently in production. Why? Because of how long objects live. Example patterns: creating objects inside tight loops → short-lived → frequent GC holding references longer than needed → objects move to old gen caching “just in case” → memory pressure builds silently Nothing looks wrong in the code. But at runtime: GC frequency increases pause times grow latency becomes unpredictable And the worst part? 👉 It doesn’t fail immediately. 👉 It degrades slowly. This is why some systems: pass load tests work fine initially then become unstable weeks later Takeaway: In Java, performance isn’t just about what you do. It’s about how long your data stays alive while doing it. #Java #JVM #Performance #Backend #SoftwareEngineering
To view or add a comment, sign in
-
🚀 Day 21 – Records in Java: The Modern Way to Model Data Java Records are a powerful feature introduced to simplify how we represent immutable data. No boilerplate. No ceremony. Just clean, minimal, and intention-driven code. Here’s what makes Records a game-changer: 🔹 1. Zero Boilerplate No need to manually write: ✔ getters ✔ constructors ✔ equals() ✔ hashCode() ✔ toString() Java auto-generates all of these. Your class becomes crystal clear about what it stores. 🔹 2. Immutable Data by Design Records are inherently final & immutable, making them: ✔ Thread-safe ✔ Predictable ✔ Side-effect-free Perfect for modern architectures using events, messages, DTOs, and API contracts. 🔹 3. Great for Domain Modeling When your class exists only to hold data — User, Order, GeoLocation, Config — Records provide a clean, concise model. 🔹 4. Perfect Fit for Microservices In distributed systems, immutability = reliability. Records shine as: ✔ DTOs ✔ API request/response models ✔ Kafka event payloads ✔ Config objects 🔹 5. Improved Readability & Maintainability A record makes your intent unmistakable: ➡ “This is a data carrier.” Nothing more. Nothing less. 🔹 6. Supports Custom Logic Too You can still add: ✔ validation ✔ static methods ✔ custom constructors ✔ business constraints …without losing the simplicity. 🔥 Architect’s Takeaway Records encourage immutable, predictable, low-boilerplate designs — exactly what you need when building scalable enterprise systems and clean domain models. Are you using Records in your project instead of POJOs? #100DaysOfJavaArchitecture #Java #JavaRecords #Microservices #CleanCode #JavaDeveloper #TechLeadership
To view or add a comment, sign in
-
-
Stop wasting memory on threads that do nothing. 🛑 If you’re building Java backends, you’ve probably seen this: More users → more threads → more RAM usage I recently explored Virtual Threads (Java 21 / Project Loom), and this concept finally clicked for me. 💡 The Problem In standard Java: 1 request = 1 Platform Thread During DB/API call → thread gets blocked It’s like a waiter standing idle while food is cooking 🍽️ 👉 Wasted resources + poor scalability 🔍 The Solution: Virtual Threads 👉 Lightweight threads managed by JVM (not OS) Cheap to create Can run thousands easily Perfect for I/O-heavy backend systems ⚙️ How it actually works (Mounting / Unmounting) 1️⃣ Mounting Virtual Thread runs on a Carrier Thread (Platform Thread) 2️⃣ I/O Call (DB/API) Your code looks blocking 3️⃣ Unmounting (Parking) Virtual Thread is paused & parked in heap memory 👉 It releases the Carrier Thread 4️⃣ Carrier Thread is free Handles another request immediately 5️⃣ Remounting (Resume) Once response comes → Virtual Thread continues 💻 The "magic" in code // Looks like blocking code Runnable task = () -> { System.out.println("Processing: " + Thread.currentThread()); String data = fetchDataFromDB(); // DB/API call System.out.println("Result: " + data); }; // Run using Virtual Thread Thread.ofVirtual().start(task); 🧩 What’s happening behind the scenes? 👉 Thread.ofVirtual() Creates a lightweight thread (stored in heap, not OS-level) 👉 During DB/API call Virtual Thread gets unmounted (parked) Carrier Thread becomes free 👉 While waiting Same thread handles other requests 👉 When response comes Scheduler remounts Virtual Thread Execution continues 📈 Result No idle threads Better resource usage Simple synchronous code High scalability without complex async code 🧠 Biggest takeaway 👉 “Code looks blocking… but system is not blocked.” That’s the mindset shift. Have you tried Virtual Threads in your services yet? Did you see any real performance improvement? 🤔 #Java #BackendEngineering #VirtualThreads #ProjectLoom #Java21 #Microservices #Performance
To view or add a comment, sign in
-
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development