<?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 Someone on Medium]]></title>
        <description><![CDATA[Stories by Someone on Medium]]></description>
        <link>https://medium.com/@computer-info-1?source=rss-ddefc37f42cb------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*EkgRNHgN-rZmJkt98uA48g.png</url>
            <title>Stories by Someone on Medium</title>
            <link>https://medium.com/@computer-info-1?source=rss-ddefc37f42cb------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 16:17:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@computer-info-1/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[Understanding Java Concurrency: The Theory Behind Threads, Race Conditions, Monitors, and Deadlock]]></title>
            <link>https://medium.com/codex/understanding-java-concurrency-the-theory-behind-threads-race-conditions-monitors-and-deadlock-7a0fbc6bb426?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/7a0fbc6bb426</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[15-day]]></category>
            <category><![CDATA[theory]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 12:15:00 GMT</pubDate>
            <atom:updated>2026-11-05T04:12:16.717Z</atom:updated>
            <content:encoded><![CDATA[<h4>From Dijkstra’s mutual exclusion to Hoare’s monitors and Coffman’s four deadlock conditions — the ideas behind today’s Java code</h4><figure><img alt="Blue wireframe blueprint showing four threads converging on a shared cube, with two interlocked padlocks beside it." src="https://cdn-images-1.medium.com/max/1024/1*RVL-yFxLvThyD8_nXwMuUA.png" /><figcaption><em>Four threads, one shared value, and decades of research behind why it goes wrong.</em></figcaption></figure><pre>class Print1To10 implements Runnable {<br>    public int sum = 0;<br><br>    public void run() {<br>        for (int i = 1; i &lt;= 10; i++) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>            sum += i;<br>        }<br>    }<br>}<br><br>class PrintEven implements Runnable {<br>    public void run() {<br>        for (int i = 2; i &lt;= 20; i += 2) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>        }<br>    }<br>}<br><br>class PrintOdd implements Runnable {<br>    public void run() {<br>        for (int i = 1; i &lt; 20; i += 2) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>        }    <br>    }<br>}<br><br>class IncrementCounter {<br>    protected int counter = 0;<br>    public int incrementCount = 100000;<br><br>    public int getCounterValue() {<br>        return counter;<br>    }<br>}<br><br>class IncrementCounterUnsynchronized extends IncrementCounter implements Runnable{<br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            counter++;<br>        }<br>    }<br>}<br><br>class IncrementCounterSynchronized extends IncrementCounter implements Runnable {<br>    private final Object lockCounter = new Object();<br>    <br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            synchronized (lockCounter) {<br>                counter++;<br>            }<br>        }<br>    }<br>}<br><br>public class java_15ThreadsRaces {<br>    public static void main(String[] args) {<br>        Print1To10 print1To10 = new Print1To10();<br><br>        Thread print1To10Task = new Thread(print1To10);<br>        Thread printEvenTask = new Thread(new PrintEven());<br>        Thread printOddTask = new Thread(new PrintOdd());<br>        <br>        Thread print5Multiple = new Thread(() -&gt; {<br>            for (int i = 5; i &lt;= 50; i += 5) {<br>                System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>            }<br>        });<br><br>        print1To10Task.start();<br>        printEvenTask.start();<br>        printOddTask.start();<br>        print5Multiple.start();<br><br>        try {<br>            print1To10Task.join();<br>            printEvenTask.join();<br>            printOddTask.join();<br>            print5Multiple.join();<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occurred: &quot; + e);<br>        } <br><br>        boolean print1To10TaskCompleted = print1To10Task.isAlive();<br>        boolean printEvenTaskCompleted = printEvenTask.isAlive();<br>        boolean printOddTaskCompleted = printOddTask.isAlive();<br>        boolean print5MultipleCompleted = print5Multiple.isAlive();<br><br>        System.out.println(<br>            !print1To10TaskCompleted &amp;&amp; <br>            !printEvenTaskCompleted &amp;&amp;<br>            !printOddTaskCompleted &amp;&amp;<br>            !print5MultipleCompleted ? &quot;All tasks complete. Result of Print1To10Numbers: &quot; + print1To10.sum : &quot;Tasks still pending&quot;<br>        );<br><br>        System.out.println();<br>        System.out.println(&quot;unsynchronized [5+ runs, showing some incorrect] Unsynchronized Run + ==================== +&quot;);<br>        for (int i = 0; i &lt; 5; i++) {<br>            IncrementCounterUnsynchronized unsynchronizedIncrement = new IncrementCounterUnsynchronized();<br>            int incrementCountUnsynchronized = unsynchronizedIncrement.incrementCount;<br><br>            Thread unsyncThread1 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread2 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread3 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread4 = new Thread(unsynchronizedIncrement);<br><br>            unsyncThread1.start();<br>            unsyncThread2.start();<br>            unsyncThread3.start();<br>            unsyncThread4.start();<br><br>            try {<br>                unsyncThread1.join();<br>                unsyncThread2.join();<br>                unsyncThread3.join();<br>                unsyncThread4.join();<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occurred: &quot; + e);<br>            }<br><br>            System.out.println(&quot;Counter Value Expected (4 threads times &quot; + incrementCountUnsynchronized + &quot;): &quot; + 4 * incrementCountUnsynchronized);<br>            System.out.println(&quot;Counter Value Actual (Run &quot; + (i+1) + &quot;): &quot; + unsynchronizedIncrement.getCounterValue());<br>        }<br>        System.out.println(&quot; + ==================== + Unsynchronized Run &quot;);<br><br>        // This works because synchronized locks another thread to interrupt 3 step process of increment<br>        // ReentrantLock was not used due to not requiring much of the methods available by it and synchronized can handle this task efficiently<br>        System.out.println(&quot;\nfixed [5+ runs, all exactly correct] Synchronized Run + ==================== +&quot;);<br>        for (int i = 0; i &lt; 5; i++) {<br>            IncrementCounterSynchronized synchronizedIncrement = new IncrementCounterSynchronized();<br>            int incrementCountSynchronized = synchronizedIncrement.incrementCount;<br><br>            Thread syncThread1 = new Thread(synchronizedIncrement);<br>            Thread syncThread2 = new Thread(synchronizedIncrement);<br>            Thread syncThread3 = new Thread(synchronizedIncrement);<br>            Thread syncThread4 = new Thread(synchronizedIncrement);<br><br>            syncThread1.start();<br>            syncThread2.start();<br>            syncThread3.start();<br>            syncThread4.start();<br><br>            try {<br>                syncThread1.join();<br>                syncThread2.join();<br>                syncThread3.join();<br>                syncThread4.join();<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occurred: &quot; + e);<br>            }<br><br>            System.out.println(&quot;Counter Value Expected (4 threads times &quot; + incrementCountSynchronized + &quot;): &quot; + 4 * incrementCountSynchronized);<br>            System.out.println(&quot;Counter Value Actual (Run &quot; + (i+1) + &quot;): &quot; + synchronizedIncrement.getCounterValue());<br>        }<br>        System.out.println(&quot; + ==================== + Synchronized Run &quot;);<br><br>        final Object lockA = new Object();<br>        final Object lockB = new Object();<br><br>        System.out.println();<br>        System.out.println(&quot;=== 1. DEMONSTRATING DEADLOCK ===&quot;);<br>        Thread deadlockIncorrectA = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockA) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock A&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockB) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock B&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        Thread deadlockIncorrectB = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockB) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock B&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockA) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock A&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        deadlockIncorrectA.setDaemon(true);<br>        deadlockIncorrectB.setDaemon(true);<br><br>        deadlockIncorrectA.start();<br>        deadlockIncorrectB.start();<br><br>        try {<br>            deadlockIncorrectA.join(2000);<br>            deadlockIncorrectB.join(2000);<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occured: &quot; + e);<br>        } <br><br>        boolean thread1Alive = deadlockIncorrectA.isAlive();<br>        boolean thread2Alive = deadlockIncorrectB.isAlive();<br>        <br>        System.out.println(&quot;Timeout expired. Thread 1 alive? &quot; + thread1Alive);<br>        System.out.println(&quot;Timeout expired. Thread 2 alive? &quot; + thread2Alive);<br>        System.out.println(thread1Alive &amp;&amp; thread2Alive ? &quot;-&gt; Result: Deadlock successfully occurred!\n&quot; : &quot;-&gt; Result: Threads finished.\n&quot;);<br><br>        final Object lockC = new Object();<br>        final Object lockD = new Object();<br>        // NOTE: not using locks A and B as they are deadlocked until main is completed,<br>        // Therefore using the same locks will show the same output and not provide a solution to deadlock<br><br>        System.out.println(&quot;=== 2. FIXING THE DEADLOCK (Consistent Order) ===&quot;);<br>        Thread deadlockCorrectA = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockC) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock C&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockD) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock D&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        Thread deadlockCorrectB = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockC) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock C&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockD) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock D&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        long startTime = System.currentTimeMillis();<br><br>        deadlockCorrectA.start();<br>        deadlockCorrectB.start();<br><br>        try {<br>            deadlockCorrectA.join(2000);<br>            deadlockCorrectB.join(2000);<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occured: &quot; + e);<br>        } <br><br>        long elapsedTime = System.currentTimeMillis() - startTime;<br>        boolean fixedThread1Alive = deadlockCorrectA.isAlive();<br>        boolean fixedThread2Alive = deadlockCorrectB.isAlive();<br>        <br>        System.out.println(&quot;Elapsed time: &quot; + elapsedTime + &quot; ms&quot;);<br>        System.out.println(&quot;Timeout expired. Thread 1 alive? &quot; + fixedThread1Alive);<br>        System.out.println(&quot;Timeout expired. Thread 2 alive? &quot; + fixedThread2Alive);<br>        System.out.println(fixedThread1Alive &amp;&amp; fixedThread2Alive ? &quot;-&gt; Result: Deadlock successfully occurred!\n&quot; : &quot;-&gt; Result: Threads finished.\n&quot;);<br>    }<br>}</pre><p>Today’s program is short, but nearly every line in it answers a question that computer scientists were asking before most programmers alive today were born. Why can two correct pieces of code produce a wrong answer together? What does it mean to take turns safely? What exactly has to be true for two threads to freeze each other?</p><p>This article is the theory companion to <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-threads-race-conditions-synchronized-and-deadlocks-1d318db3f6f6">the practical walkthrough of today’s code</a>. Last lesson, we looked at <a href="https://medium.com/@computer-info-1/understanding-java-streams-the-theory-behind-lazy-evaluation-folds-and-the-optional-type-a680e8492df3">the theory behind lazy evaluation, folds, and Optional</a>, and the <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1">Streams guide</a> showed it in code. Streams were about values flowing through a pipeline. Today is about what happens when several flows touch the same value at the same time.</p><h3>Sequential Processes and the Problem of Interleaving</h3><h4>Why the output of four threads has no fixed order</h4><p>In the 1960s, Edsger W. Dijkstra’s essay “Cooperating Sequential Processes” (EWD123, 1965) framed concurrent programs as a set of ordinary sequential processes that occasionally need to coordinate. A key idea was that the relative speeds of those processes are unknown and arbitrary. A correct program may not depend on one process being faster than another.</p><p>That idea is what the first block of our program demonstrates:</p><pre>print1To10Task.start();<br>printEvenTask.start();<br>printOddTask.start();<br>print5Multiple.start();</pre><p>Each Thread is a sequential process. Within Thread-1, 2 is always printed before 4. Across threads, the JVM scheduler and the operating system decide who runs when, so the lines may appear grouped or mixed, and either is a valid outcome. Programs that only work because one thread happened to be quicker are exactly what Dijkstra&#39;s principle rules out.</p><p>The same line of thinking explains why join() exists. It is the point where one process waits for another to finish, so main can safely read print1To10.sum afterwards.</p><h3>Critical Sections and Mutual Exclusion</h3><h4>The problem that gave counter++ its danger</h4><p>In 1965, Dijkstra published “Solution of a Problem in Concurrent Programming Control” in <em>Communications of the ACM</em> (volume 8, issue 9). The problem it stated is one that still defines the field: several processes, each with a stretch of code that must never run in two places at once. That stretch is called a critical section, and keeping processes out of each other’s critical sections is called mutual exclusion. Dijkstra’s paper gave a solution using only ordinary reads and writes of shared variables.</p><p>In our code, the critical section is one line:</p><pre>for (int i = 0; i &lt; incrementCount; i++) {<br>    counter++;<br>}</pre><p>The line looks tiny, but it contains three separate actions: read counter, add one, write the result back. If two threads are between &quot;read&quot; and &quot;write&quot; at the same moment, each overwrites the other&#39;s work. In the language of the theory, the critical section was not protected, so the final total is no longer the sum of the work done.</p><h3>Races, Atomicity, and the Java Memory Model</h3><h4>Why 400,000 became 135,072</h4><p>One recorded run of the unsynchronized version produced this:</p><pre>Counter Value Expected (4 threads times 100000): 400000<br>Counter Value Actual (Run 1): 135072</pre><p>Brian Goetz and co-authors, in <em>Java Concurrency in Practice</em> (Addison-Wesley, 2006), call this pattern a read-modify-write sequence and explain that it is a race condition: the result depends on the unlucky timing of threads. The fix needs the three steps to be atomic, meaning indivisible from the viewpoint of every other thread.</p><p>There is a second, deeper problem hiding in this code. Chapter 17 of the Java Language Specification defines what a thread is guaranteed to see when another thread writes a value. It does this through a relation called happens-before, a term borrowed from Leslie Lamport’s 1978 paper “Time, Clocks, and the Ordering of Events in a Distributed System” in <em>Communications of the ACM</em>. The Java Memory Model itself is described by Jeremy Manson, William Pugh, and Sarita Adve in “The Java Memory Model” (POPL 2005). When two threads access the same variable, at least one access is a write, and nothing orders the accesses by happens-before, the specification calls it a data race. Our unprotected counter is exactly that. It is not only that the arithmetic can be lost, but that one thread is not even promised to see another&#39;s latest write.</p><p>Some of that ordering is provided for free. The specification states that all actions in a thread happen-before another thread successfully returns from join() on it. That is why this works:</p><pre>print1To10Task.join();<br>// ...<br>System.out.println(&quot;... &quot; + print1To10.sum);</pre><p>The thread wrote sum, and join() creates the ordering that makes that write visible to main.</p><h3>Monitors: Turning a Rule into a Language Feature</h3><h4>What synchronized really is</h4><p>C. A. R. Hoare’s “Monitors: An Operating System Structuring Concept” (<em>Communications of the ACM</em>, October 1974, pp. 549–557) proposed gathering shared data together with the only code allowed to touch it, and having the system guarantee that one process at a time is inside. Per Brinch Hansen was developing the same idea at the same time, and the two are usually credited together. The point was to stop relying on programmers remembering to guard every access by hand.</p><p>Java adopted the monitor idea. The Java Language Specification, in its chapter on threads and locks, describes synchronization as implemented using monitors, with one monitor associated with every object. Our fix uses exactly that:</p><pre>private final Object lockCounter = new Object();<br><br>public void run() {<br>    for (int i = 0; i &lt; incrementCount; i++) {<br>        synchronized (lockCounter) {<br>            counter++;<br>        }<br>    }<br>}</pre><p>Entering the block acquires the monitor of lockCounter, and leaving it releases the monitor, even if an exception is thrown. This is where Java differs from Hoare&#39;s original design: Hoare bound a monitor to a data structure, whereas Java lets any object serve as the lock, and the programmer chooses which one. That freedom is also a responsibility. Protection only exists if every thread contending for the data uses the same lock object, which is why all four threads in our run share a single IncrementCounterSynchronized instance.</p><p>The monitor also carries the memory guarantee from the previous section. In the specification’s happens-before rules, releasing a monitor happens-before every later acquisition of the same monitor. So the lock does two jobs at once: it provides mutual exclusion, and it makes each thread’s write visible to the next. That second job is why every one of our synchronized runs reports exactly 400,000.</p><h3>Deadlock and the Four Coffman Conditions</h3><h4>Exactly what has to be true for two threads to freeze</h4><p>In 1971, E. G. Coffman, M. J. Elphick, and A. Shoshani published “System Deadlocks” in <em>ACM Computing Surveys</em> (volume 3, issue 2, pages 67–78). They identified four conditions that must all hold at the same time for a deadlock to occur. Each of them can be pointed to in the deadlock demo:</p><ul><li>Mutual exclusion: a lock can be held by only one thread. This is the synchronized block itself.</li><li>Hold and wait: a thread holds one resource while waiting for another. Thread A holds lockA while it asks for lockB, and the Thread.sleep(50) makes sure that both threads reach this state.</li><li>No preemption: a resource cannot be taken away from its holder. A Java monitor is released only when the holding thread leaves its block, and a thread stuck waiting to enter a synchronized block cannot be talked out of it by an interrupt.</li><li>Circular wait: there is a closed chain of threads, each waiting for a resource held by the next. A waits for B’s lock, and B waits for A’s.</li></ul><pre>// Thread A                          // Thread B<br>synchronized (lockA) {               synchronized (lockB) {<br>    Thread.sleep(50);                    Thread.sleep(50);<br>    synchronized (lockB) { ... }         synchronized (lockA) { ... }<br>}                                    }</pre><p>The theory also explains the shape of the classic puzzle. Dijkstra set the dining philosophers problem as an exam question in 1965, and Hoare later gave it the form we know today: five philosophers, five forks, and each philosopher needs two forks to eat. If every philosopher picks up the left fork first, all of them can end up holding one fork and waiting forever. It is our two-lock program with more threads. Dijkstra’s treatment appeared in “Hierarchical Ordering of Sequential Processes” (<em>Acta Informatica</em>, volume 1, issue 2, 1971, pp. 115–138).</p><h3>Breaking the Cycle: Why Lock Ordering Works</h3><h4>Removing one condition is enough</h4><p>Because a deadlock needs all four conditions, removing any one of them prevents it. We cannot give up mutual exclusion, because that would bring back the race. We cannot cheaply give up hold-and-wait or no-preemption with plain synchronized. The practical condition to attack is circular wait, and the standard way is resource ordering: give every lock a rank and require everyone to acquire locks in rank order.</p><pre>// Both threads now take lockC first, then lockD<br>synchronized (lockC) {<br>    Thread.sleep(50);<br>    synchronized (lockD) { ... }<br>}</pre><p>If every thread follows the same order, a cycle cannot form. A thread holding lockD can never be waiting for lockC, because no thread ever asks for lockC while already holding lockD. This ordering discipline is also the resource-hierarchy solution to the dining philosophers problem, and <em>Java Concurrency in Practice</em> devotes a chapter on liveness hazards to lock-ordering deadlocks.</p><p>There is a price. The two threads now run one after the other through the locked section, and the measured 102 ms is the two sleep(50) calls back to back. Prevention trades some concurrency for the guarantee that the system cannot freeze. For our program that is clearly worth it.</p><h3>Proving a Deadlock Without Living Through It</h3><h4>Timeouts, daemon threads, and the limits of detection</h4><p>The program has to prove a deadlock happened without hanging forever, and it does so in two ways:</p><pre>deadlockIncorrectA.setDaemon(true);<br>deadlockIncorrectB.setDaemon(true);<br>// ...<br>deadlockIncorrectA.join(2000);<br>deadlockIncorrectB.join(2000);<br>System.out.println(&quot;Timeout expired. Thread 1 alive? &quot; + deadlockIncorrectA.isAlive());</pre><p>The Java Language Specification’s section on program exit says the JVM exits once every non-daemon thread has finished. By marking the stuck threads as daemons, main can finish and let the program end. The timed join and isAlive() are a practical detection method: if both threads are still alive after two seconds, they are blocked.</p><p>This is a timeout, not a proof. A slow thread and a deadlocked thread look identical from outside, which is why the theory prefers to detect the cycle itself. For monitor locks, the JVM can do this: a thread dump from jstack, or the ThreadMXBean API, can report that threads are deadlocked and name the locks involved.</p><h3>Putting It Together</h3><h4>Each idea, its origin, and the line of code that carries it</h4><p>Interleaving with unknown speeds comes from Dijkstra’s sequential-process model, and it shows up in the four start() calls. Critical sections and mutual exclusion come from Dijkstra&#39;s 1965 work, and they show up in counter++. Atomicity, data races, and happens-before come from the Java Language Specification and the Java Memory Model research, and they show up in the result 135072 and in the join() before sum is read. Monitors come from Hoare and Brinch Hansen, and they show up in synchronized (lockCounter). Deadlock and its four conditions come from Coffman, Elphick, and Shoshani, and they show up in lockA and lockB. Resource ordering comes from the dining philosophers literature, and it shows up in lockC and lockD.</p><p>The recurring theme of this series, behavior that is silently wrong, is at its sharpest here. A race condition raises no exception. A passing run proves nothing. The theory gives us a way to reason about correctness that does not depend on getting lucky.</p><h3>Further Reading</h3><p>Dijkstra, E. W. “Cooperating Sequential Processes” (EWD123), 1965.</p><p>Dijkstra, E. W. “Solution of a Problem in Concurrent Programming Control.” <em>Communications of the ACM</em> 8(9), 1965.</p><p>Dijkstra, E. W. “Hierarchical Ordering of Sequential Processes.” <em>Acta Informatica</em> 1(2), 1971, pp. 115–138.</p><p>Coffman, E. G., Elphick, M. J., and Shoshani, A. “System Deadlocks.” <em>ACM Computing Surveys</em> 3(2), 1971, pp. 67–78.</p><p>Hoare, C. A. R. “Monitors: An Operating System Structuring Concept.” <em>Communications of the ACM</em>, October 1974, pp. 549–557.</p><p>Lamport, L. “Time, Clocks, and the Ordering of Events in a Distributed System.” <em>Communications of the ACM</em>, 1978.</p><p>Manson, J., Pugh, W., and Adve, S. “The Java Memory Model.” POPL 2005.</p><p>Goetz, B. et al. <em>Java Concurrency in Practice.</em> Addison-Wesley, 2006.</p><p><em>The Java Language Specification</em>, chapters 12 (program exit) and 17 (threads and locks).</p><h3>The Arbiter Journey So Far</h3><p>This is lesson 15 of the Arbiter Learning Journey, a Java-from-scratch series building toward Arbiter, a microservices-based test management platform. Every concept is written and tested by hand before the next one begins. A test platform runs many things at once, so the ideas in this article, safe shared state and lock discipline, will matter every time Arbiter’s services handle more than one request.</p><h3>Understand the concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FG2KOnQcLVgo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DG2KOnQcLVgo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FG2KOnQcLVgo%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/079e27dc531c98c338881b448de6e1fb/href">https://medium.com/media/079e27dc531c98c338881b448de6e1fb/href</a></iframe><h3>Arbiter YouTube Playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><h3>Arbiter on GitHub</h3><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Arbiter Lessons</h3><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Other series on this account</h3><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next</h3><p>Our program created 48 Thread objects by hand. The next lesson hands that job to the JVM with ExecutorService and thread pools, followed by CompletableFuture for results that arrive later.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a0fbc6bb426" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/understanding-java-concurrency-the-theory-behind-threads-race-conditions-monitors-and-deadlock-7a0fbc6bb426">Understanding Java Concurrency: The Theory Behind Threads, Race Conditions, Monitors, and Deadlock</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Java from Scratch with Arbiter: Threads, Race Conditions, synchronized, and Deadlocks]]></title>
            <link>https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-threads-race-conditions-synchronized-and-deadlocks-1d318db3f6f6?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/1d318db3f6f6</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[15-day]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[concurrency]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 12:07:18 GMT</pubDate>
            <atom:updated>2026-11-05T04:11:38.663Z</atom:updated>
            <content:encoded><![CDATA[<h4>Four threads, one counter, and a silently wrong total — fixed with synchronized, plus a deadlock caused and cured on purpose</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s7xACCjZ3tC3s6OUB-xMkA.png" /><figcaption><em>Four threads race toward one shared counter — and sometimes the total quietly comes out wrong.</em></figcaption></figure><pre>class Print1To10 implements Runnable {<br>    public int sum = 0;<br><br>    public void run() {<br>        for (int i = 1; i &lt;= 10; i++) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>            sum += i;<br>        }<br>    }<br>}<br><br>class PrintEven implements Runnable {<br>    public void run() {<br>        for (int i = 2; i &lt;= 20; i += 2) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>        }<br>    }<br>}<br><br>class PrintOdd implements Runnable {<br>    public void run() {<br>        for (int i = 1; i &lt; 20; i += 2) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>        }    <br>    }<br>}<br><br>class IncrementCounter {<br>    protected int counter = 0;<br>    public int incrementCount = 100000;<br><br>    public int getCounterValue() {<br>        return counter;<br>    }<br>}<br><br>class IncrementCounterUnsynchronized extends IncrementCounter implements Runnable{<br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            counter++;<br>        }<br>    }<br>}<br><br>class IncrementCounterSynchronized extends IncrementCounter implements Runnable {<br>    private final Object lockCounter = new Object();<br>    <br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            synchronized (lockCounter) {<br>                counter++;<br>            }<br>        }<br>    }<br>}<br><br>public class java_15ThreadsRaces {<br>    public static void main(String[] args) {<br>        Print1To10 print1To10 = new Print1To10();<br><br>        Thread print1To10Task = new Thread(print1To10);<br>        Thread printEvenTask = new Thread(new PrintEven());<br>        Thread printOddTask = new Thread(new PrintOdd());<br>        <br>        Thread print5Multiple = new Thread(() -&gt; {<br>            for (int i = 5; i &lt;= 50; i += 5) {<br>                System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>            }<br>        });<br><br>        print1To10Task.start();<br>        printEvenTask.start();<br>        printOddTask.start();<br>        print5Multiple.start();<br><br>        try {<br>            print1To10Task.join();<br>            printEvenTask.join();<br>            printOddTask.join();<br>            print5Multiple.join();<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occurred: &quot; + e);<br>        } <br><br>        boolean print1To10TaskCompleted = print1To10Task.isAlive();<br>        boolean printEvenTaskCompleted = printEvenTask.isAlive();<br>        boolean printOddTaskCompleted = printOddTask.isAlive();<br>        boolean print5MultipleCompleted = print5Multiple.isAlive();<br><br>        System.out.println(<br>            !print1To10TaskCompleted &amp;&amp; <br>            !printEvenTaskCompleted &amp;&amp;<br>            !printOddTaskCompleted &amp;&amp;<br>            !print5MultipleCompleted ? &quot;All tasks complete. Result of Print1To10Numbers: &quot; + print1To10.sum : &quot;Tasks still pending&quot;<br>        );<br><br>        System.out.println();<br>        System.out.println(&quot;unsynchronized [5+ runs, showing some incorrect] Unsynchronized Run + ==================== +&quot;);<br>        for (int i = 0; i &lt; 5; i++) {<br>            IncrementCounterUnsynchronized unsynchronizedIncrement = new IncrementCounterUnsynchronized();<br>            int incrementCountUnsynchronized = unsynchronizedIncrement.incrementCount;<br><br>            Thread unsyncThread1 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread2 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread3 = new Thread(unsynchronizedIncrement);<br>            Thread unsyncThread4 = new Thread(unsynchronizedIncrement);<br><br>            unsyncThread1.start();<br>            unsyncThread2.start();<br>            unsyncThread3.start();<br>            unsyncThread4.start();<br><br>            try {<br>                unsyncThread1.join();<br>                unsyncThread2.join();<br>                unsyncThread3.join();<br>                unsyncThread4.join();<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occurred: &quot; + e);<br>            }<br><br>            System.out.println(&quot;Counter Value Expected (4 threads times &quot; + incrementCountUnsynchronized + &quot;): &quot; + 4 * incrementCountUnsynchronized);<br>            System.out.println(&quot;Counter Value Actual (Run &quot; + (i+1) + &quot;): &quot; + unsynchronizedIncrement.getCounterValue());<br>        }<br>        System.out.println(&quot; + ==================== + Unsynchronized Run &quot;);<br><br>        // This works because synchronized locks another thread to interrupt 3 step process of increment<br>        // ReentrantLock was not used due to not requiring much of the methods available by it and synchronized can handle this task efficiently<br>        System.out.println(&quot;\nfixed [5+ runs, all exactly correct] Synchronized Run + ==================== +&quot;);<br>        for (int i = 0; i &lt; 5; i++) {<br>            IncrementCounterSynchronized synchronizedIncrement = new IncrementCounterSynchronized();<br>            int incrementCountSynchronized = synchronizedIncrement.incrementCount;<br><br>            Thread syncThread1 = new Thread(synchronizedIncrement);<br>            Thread syncThread2 = new Thread(synchronizedIncrement);<br>            Thread syncThread3 = new Thread(synchronizedIncrement);<br>            Thread syncThread4 = new Thread(synchronizedIncrement);<br><br>            syncThread1.start();<br>            syncThread2.start();<br>            syncThread3.start();<br>            syncThread4.start();<br><br>            try {<br>                syncThread1.join();<br>                syncThread2.join();<br>                syncThread3.join();<br>                syncThread4.join();<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occurred: &quot; + e);<br>            }<br><br>            System.out.println(&quot;Counter Value Expected (4 threads times &quot; + incrementCountSynchronized + &quot;): &quot; + 4 * incrementCountSynchronized);<br>            System.out.println(&quot;Counter Value Actual (Run &quot; + (i+1) + &quot;): &quot; + synchronizedIncrement.getCounterValue());<br>        }<br>        System.out.println(&quot; + ==================== + Synchronized Run &quot;);<br><br>        final Object lockA = new Object();<br>        final Object lockB = new Object();<br><br>        System.out.println();<br>        System.out.println(&quot;=== 1. DEMONSTRATING DEADLOCK ===&quot;);<br>        Thread deadlockIncorrectA = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockA) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock A&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockB) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock B&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        Thread deadlockIncorrectB = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockB) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock B&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockA) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock A&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        deadlockIncorrectA.setDaemon(true);<br>        deadlockIncorrectB.setDaemon(true);<br><br>        deadlockIncorrectA.start();<br>        deadlockIncorrectB.start();<br><br>        try {<br>            deadlockIncorrectA.join(2000);<br>            deadlockIncorrectB.join(2000);<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occured: &quot; + e);<br>        } <br><br>        boolean thread1Alive = deadlockIncorrectA.isAlive();<br>        boolean thread2Alive = deadlockIncorrectB.isAlive();<br>        <br>        System.out.println(&quot;Timeout expired. Thread 1 alive? &quot; + thread1Alive);<br>        System.out.println(&quot;Timeout expired. Thread 2 alive? &quot; + thread2Alive);<br>        System.out.println(thread1Alive &amp;&amp; thread2Alive ? &quot;-&gt; Result: Deadlock successfully occurred!\n&quot; : &quot;-&gt; Result: Threads finished.\n&quot;);<br><br>        final Object lockC = new Object();<br>        final Object lockD = new Object();<br>        // NOTE: not using locks A and B as they are deadlocked until main is completed,<br>        // Therefore using the same locks will show the same output and not provide a solution to deadlock<br><br>        System.out.println(&quot;=== 2. FIXING THE DEADLOCK (Consistent Order) ===&quot;);<br>        Thread deadlockCorrectA = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockC) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock C&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockD) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock D&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        Thread deadlockCorrectB = new Thread(() -&gt; {<br>            try {<br>                synchronized (lockC) {<br>                    System.out.println(Thread.currentThread().getName() + &quot; acquired Lock C&quot;);<br>                    Thread.sleep(50);<br>                    <br>                    synchronized (lockD) {<br>                        System.out.println(Thread.currentThread().getName() + &quot; acquired Lock D&quot;);<br>                    }<br>                }<br>            } catch (InterruptedException e) {<br>                System.out.println(&quot;Exception Occured: &quot; + e);<br>            }<br>        });<br><br>        long startTime = System.currentTimeMillis();<br><br>        deadlockCorrectA.start();<br>        deadlockCorrectB.start();<br><br>        try {<br>            deadlockCorrectA.join(2000);<br>            deadlockCorrectB.join(2000);<br>        } catch (InterruptedException e) {<br>            System.out.println(&quot;Exception Occured: &quot; + e);<br>        } <br><br>        long elapsedTime = System.currentTimeMillis() - startTime;<br>        boolean fixedThread1Alive = deadlockCorrectA.isAlive();<br>        boolean fixedThread2Alive = deadlockCorrectB.isAlive();<br>        <br>        System.out.println(&quot;Elapsed time: &quot; + elapsedTime + &quot; ms&quot;);<br>        System.out.println(&quot;Timeout expired. Thread 1 alive? &quot; + fixedThread1Alive);<br>        System.out.println(&quot;Timeout expired. Thread 2 alive? &quot; + fixedThread2Alive);<br>        System.out.println(fixedThread1Alive &amp;&amp; fixedThread2Alive ? &quot;-&gt; Result: Deadlock successfully occurred!\n&quot; : &quot;-&gt; Result: Threads finished.\n&quot;);<br>    }<br>}</pre><p>Until today, every program in this series has done one thing at a time. A line ran, finished, and the next line ran. That is a comfortable way to write code, and it is about to stop being true.</p><p>In <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1">the last lesson</a>, we built lazy Streams pipelines with reduce and groupingBy. The theory behind them is in the <a href="https://medium.com/@computer-info-1/understanding-java-streams-the-theory-behind-lazy-evaluation-folds-and-the-optional-type-a680e8492df3">companion article on lazy evaluation, folds, and Optional</a>. Today we make Java do several things at once, and we meet the first bug in this series that never throws an exception, never prints a warning, and sometimes doesn&#39;t even happen.</p><p>The program above does four things:</p><ul><li>Runs four independent tasks on four threads at the same time</li><li>Lets four threads increment one shared counter with no protection, and watches the total go wrong</li><li>Fixes the counter with synchronized</li><li>Builds a deadlock on purpose, proves it happened, then removes it with one rule</li></ul><h3>Thread and Runnable: Describing Work vs. Running It</h3><p>In Java, a Runnable is a piece of work and a Thread is the worker that performs it. Describing the work is the easy part:</p><pre>class PrintEven implements Runnable {<br>    public void run() {<br>        for (int i = 2; i &lt;= 20; i += 2) {<br>            System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>        }<br>    }<br>}</pre><p>Runnable has exactly one method, run(). Because it has only one abstract method, it can also be written as a lambda, the same arrow syntax we used with Streams:</p><pre>Thread print5Multiple = new Thread(() -&gt; {<br>    for (int i = 5; i &lt;= 50; i += 5) {<br>        System.out.println(&quot;Thread Name: &quot; + Thread.currentThread().getName() + &quot;, task value: &quot; + i);<br>    }<br>});</pre><p>Our program creates four threads this way: Print1To10 (a class that also stores a sum), PrintEven, PrintOdd, and the lambda above. Thread.currentThread().getName() is how each line reveals which thread printed it.</p><p>The most important rule is the next one. Calling .start() creates a new thread of execution. Calling .run() directly just runs the method on the thread you are already on, like any ordinary method call. Nothing concurrent happens.</p><pre>print1To10Task.start();<br>printEvenTask.start();<br>printOddTask.start();<br>print5Multiple.start();</pre><h3>The Output Order Is Not Yours to Control</h3><p>Run this program a few times and the first block of output changes. In some runs Thread-0 prints all ten of its numbers before any other thread says a word. In other runs the lines from Thread-1, Thread-2, and Thread-3 are mixed together. The JVM and the operating system decide who runs when, and each task here is so short that one thread can finish before the next has been scheduled.</p><p>Both orderings are correct. The only guarantee we have is within a single thread: Thread-1 will always print 2 before 4 before 6. Across threads, there is no promised order.</p><h3>Start Everything First, Join Afterwards</h3><pre>print1To10Task.start();<br>printEvenTask.start();<br>printOddTask.start();<br>print5Multiple.start();<br><br>print1To10Task.join();<br>printEvenTask.join();<br>printOddTask.join();<br>print5Multiple.join();</pre><p>join() makes the calling thread, here main, wait until that thread&#39;s run() has returned. The order matters. All four start() calls come first, so all four threads are running before main waits for anything. If we wrote start(); join(); start(); join(); for each thread, every thread would have to finish before the next one began, and we would have rebuilt a sequential program with extra steps.</p><p>After the joins, main checks isAlive() on each thread and prints the sum that Print1To10 accumulated:</p><pre>All tasks complete. Result of Print1To10Numbers: 55</pre><p>That 55 is reliable because join() guarantees that everything the thread wrote is visible to main once join() returns.</p><h3>The Counter That Lies</h3><p>Now the interesting part. Four threads, each running the same loop 100,000 times, all incrementing one shared field:</p><pre>class IncrementCounterUnsynchronized extends IncrementCounter implements Runnable {<br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            counter++;<br>        }<br>    }<br>}</pre><p>All four threads are handed the same IncrementCounterUnsynchronized object, so they share one counter. The expected result is 4 x 100,000 = 400,000. Here is what one recorded run produced:</p><pre>Counter Value Expected (4 threads times 100000): 400000<br>Counter Value Actual (Run 1): 135072</pre><p>Nothing crashed and no exception was thrown. The program simply reported a wrong number, confidently.</p><p>The reason is that counter++ looks like one operation but is three: read the current value, add one, write the result back. Two threads can read the same value before either writes. Both compute the same new value and both write it. Two increments happened, but the counter only moved by one. This is called a lost update.</p><figure><img alt="Two timelines comparing an unprotected counter, where two threads read 5 and both write 6, with a locked counter where the second thread waits and the result is 7." src="https://cdn-images-1.medium.com/max/1024/1*uosxhtdPxyaErso53EkSfQ.png" /><figcaption><em>Without a lock, two threads can read the same value and overwrite each other. With a lock, each read-add-write finishes before the next begins.</em></figcaption></figure><p>There is a second, quieter lesson here. In the loop of five runs, some iterations come out exactly right by luck. A run that passes proves nothing. A race condition is a probability, not a certainty, which is also why it only shows up reliably at 400,000 increments and would be easy to miss at 10.</p><h3>synchronized: One Thread at a Time</h3><p>The fix gives every thread the same lock object and makes the increment happen inside it:</p><pre>class IncrementCounterSynchronized extends IncrementCounter implements Runnable {<br>    private final Object lockCounter = new Object();<br><br>    public void run() {<br>        for (int i = 0; i &lt; incrementCount; i++) {<br>            synchronized (lockCounter) {<br>                counter++;<br>            }<br>        }<br>    }<br>}</pre><p>Every Java object has an invisible lock, called a monitor. Entering a synchronized (lockCounter) block means taking that monitor, and only one thread can hold it at a time. While one thread is inside, the others wait at the door. That makes read, add, and write behave as one indivisible step.</p><p>Two details are easy to miss. First, the lock only works because all four threads share the same instance, and so the same lockCounter. If each thread had its own object, each would have its own lock and nothing would be protected. Second, the block releases the lock when it exits, even if an exception is thrown inside it, so the lock cannot be forgotten.</p><p>We could have used ReentrantLock, but we only need basic mutual exclusion, and synchronized handles that with less code. The result across all five runs:</p><pre>Counter Value Expected (4 threads times 100000): 400000<br>Counter Value Actual (Run 1): 400000<br>...<br>Counter Value Actual (Run 5): 400000</pre><h3>Deadlock: Two Locks, Opposite Order</h3><p>A lock fixes the race, but locks create a new way to fail. Here two threads each need two locks, and they ask for them in opposite orders:</p><pre>// Thread A                          // Thread B<br>synchronized (lockA) {               synchronized (lockB) {<br>    Thread.sleep(50);                    Thread.sleep(50);<br>    synchronized (lockB) { ... }         synchronized (lockA) { ... }<br>}                                    }</pre><p>Thread A takes lockA, Thread B takes lockB, and the sleep(50) makes sure both have done so before either asks for its second lock. Now A waits for lockB, which B holds, and B waits for lockA, which A holds. Neither can leave its block to release what it holds until it has the second lock. They will wait forever.</p><p>A program that hangs forever is a bad way to prove a point, so the code uses two safety nets:</p><pre>deadlockIncorrectA.setDaemon(true);<br>deadlockIncorrectB.setDaemon(true);<br><br>deadlockIncorrectA.start();<br>deadlockIncorrectB.start();<br><br>deadlockIncorrectA.join(2000);<br>deadlockIncorrectB.join(2000);</pre><p>setDaemon(true) marks the threads as background threads, so the JVM can still exit when main finishes even though they are stuck. join(2000) waits at most two seconds instead of forever. After that, isAlive() is the evidence:</p><pre>=== 1. DEMONSTRATING DEADLOCK ===<br>Thread-44 acquired Lock A<br>Thread-45 acquired Lock B<br>Timeout expired. Thread 1 alive? true<br>Timeout expired. Thread 2 alive? true<br>-&gt; Result: Deadlock successfully occurred!</pre><p>Notice that each thread printed that it got its first lock, and neither ever printed the second. Both still alive after the timeout means they are blocked, not just slow. (The thread numbers 44 and 45 are only a global counter, and they show how many threads this program had already created by that point.)</p><h3>The Fix: One Order for Everyone</h3><p>The cure is not fewer locks. It is the same order every time:</p><pre>// Thread A                          // Thread B<br>synchronized (lockC) {               synchronized (lockC) {<br>    Thread.sleep(50);                    Thread.sleep(50);<br>    synchronized (lockD) { ... }         synchronized (lockD) { ... }<br>}                                    }</pre><p>Both threads now take lockC first, then lockD. Whoever wins lockC makes the other wait at the first lock, and nobody can ever hold lockD while waiting for lockC, so a circular wait cannot form. We use fresh locks C and D on purpose: A and B are still held by the stuck daemon threads, so reusing them would just reproduce the deadlock.</p><pre>=== 2. FIXING THE DEADLOCK (Consistent Order) ===<br>Thread-46 acquired Lock C<br>Thread-46 acquired Lock D<br>Thread-47 acquired Lock C<br>Thread-47 acquired Lock D<br>Elapsed time: 102 ms<br>Timeout expired. Thread 1 alive? false<br>Timeout expired. Thread 2 alive? false<br>-&gt; Result: Threads finished.</pre><p>The two threads now run one after the other instead of side by side, and the elapsed time of about 102 ms shows it: two sleep(50) calls back to back. Slightly sequential and correct is a far better outcome than concurrent and frozen. The thread names also show they never overlapped, with Thread-46 completing both locks before Thread-47 began.</p><h3>Key Takeaways</h3><ul><li>.start() creates a thread; .run() is just a method call</li><li>Start all threads before joining any of them, or the work becomes sequential again</li><li>Output order across threads is not guaranteed and changes between runs</li><li>counter++ is three steps, and unprotected shared state can lose updates with no error at all</li><li>A passing run does not prove a race condition is absent</li><li>synchronized on one shared lock object makes the read-add-write step indivisible</li><li>Two threads taking two locks in opposite orders can deadlock; taking them in the same order cannot</li><li>join(timeout) plus isAlive() lets you prove a deadlock without hanging the program</li></ul><h3>The Arbiter Journey So Far</h3><p>This is lesson 15 of the Arbiter Learning Journey, a Java-from-scratch series building toward Arbiter, a microservices-based test management platform. Every lesson comes with a runnable source file, and every concept is written and tested by hand before moving on. A test platform runs many things at once, such as test executions, notifications, and API requests, so today’s lesson is groundwork for what comes later.</p><h3>Understand the concepts in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FG2KOnQcLVgo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DG2KOnQcLVgo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FG2KOnQcLVgo%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/079e27dc531c98c338881b448de6e1fb/href">https://medium.com/media/079e27dc531c98c338881b448de6e1fb/href</a></iframe><h3><strong>Watch the series on YouTube</strong></h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c6900b57bc65ab9363a1ee4b0b5c6d51/href">https://medium.com/media/c6900b57bc65ab9363a1ee4b0b5c6d51/href</a></iframe><h3><strong>Code on GitHub</strong></h3><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Arbiter Lessons</h3><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Other series on this account</h3><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next</h3><p>This program created 48 Thread objects by hand, and that is already awkward for what is a very small amount of work. In the next lesson we hand that job to the JVM with ExecutorService and thread pools, and then look at CompletableFuture for results that arrive later.</p><p>Read the companion <a href="https://computer-info-1.medium.com/understanding-java-concurrency-the-theory-behind-threads-race-conditions-monitors-and-deadlock-7a0fbc6bb426?sharedUserId=computer-info-1">theory article here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1d318db3f6f6" width="1" height="1" alt=""><hr><p><a href="https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-threads-race-conditions-synchronized-and-deadlocks-1d318db3f6f6">Learning Java from Scratch with Arbiter: Threads, Race Conditions, synchronized, and Deadlocks</a> was originally published in <a href="https://blog.devgenius.io">Dev Genius</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding the Linux Command Line — The Theory Behind Options, Word Splitting, and How a…]]></title>
            <link>https://medium.com/codex/understanding-the-linux-command-line-the-theory-behind-options-word-splitting-and-how-a-ee9376b6a071?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee9376b6a071</guid>
            <category><![CDATA[unix]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[infosec]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[bash]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 07:56:01 GMT</pubDate>
            <atom:updated>2026-11-02T13:37:26.655Z</atom:updated>
            <content:encoded><![CDATA[<h3>Understanding the Linux Command Line — The Theory Behind Options, Word Splitting, and How a Filename Becomes an Attack</h3><h4>Breachlab Guide Theory • Day 2 | Why “options” are only a convention, why the shell splits your words before any program sees them, and how that gap turned into a real class of vulnerabilities</h4><figure><img alt="Diagram showing a dash-prefixed filename routed to options, a filename with a space split in two, and four fixes below" src="https://cdn-images-1.medium.com/max/1024/1*irUOdMj3BUdbEMp7IyhwFA.png" /><figcaption>A leading dash turns a filename into an option, and a space splits one name into two. Four fixes remove the ambiguity.</figcaption></figure><p><em>Disclaimer: This series is for educational purposes only. Everything discussed is studied on Breachlab’s legal practice targets and through published research. Don’t test these ideas on systems you don’t own or don’t have written permission to test. In line with Breachlab’s guidelines, this article explains concepts and history; it contains no level answers or step-by-step solutions.</em></p><p>In the practical guide for Ghost Level 1, a file that was plainly visible in ls refused to open. A leading dash made cat treat the name as an option, a lone dash made it read the keyboard, and a space split one filename into two.</p><p>It’s tempting to file that under “Linux quirks” and move on. But when you ask <em>why</em> it happens, you uncover one of the oldest and most persistent ideas in Unix security. A command line goes through two interpreters before anything is executed, and neither of them knows what the other intended.</p><p>This article accompanies the practical guide for Day 2 and focuses on the reasoning behind the behaviour rather than on typing commands. We’ll look at where “options” actually come from, what the shell does to your text before any program sees it, why Linux allows such strange filenames at all, and how security researchers turned this ambiguity into real attacks.</p><h3>The Kernel Has No Idea What an Option Is</h3><p><em>Why the dash is a social agreement between programs, not a feature of the operating system.</em></p><p>When the shell starts a program, it eventually calls execve, the system call that replaces a process with a new program. One of its parameters is argv: an array of plain strings. That&#39;s it. The kernel doesn&#39;t mark any string as &quot;option&quot; or &quot;filename&quot;. -v, notes.txt and --help all arrive as identical, unlabelled pieces of text.</p><p>Deciding what each string <em>means</em> is entirely the job of the program receiving it. Early Unix programs each parsed their own arguments by hand, and a habit formed: arguments starting with - are switches, and everything else is something to work on. That habit was later formalised by the getopt library function and then written into the POSIX standard as the <strong>Utility Syntax Guidelines</strong>.</p><p>Three of those guidelines explain almost everything you saw on Level 1:</p><ul><li><strong>Guideline 4</strong> says all options should be preceded by the - delimiter character. This is the rule that turns -v into a switch.</li><li><strong>Guideline 10</strong> says the first -- that isn&#39;t an option-argument marks the end of options. That&#39;s why cat -- -v works.</li><li><strong>Guideline 13</strong> says a - operand should mean only standard input, or a file named -. The standard itself admits the ambiguity, and leaves it to each utility to choose.</li></ul><p>Notice the language: <em>should</em>. These are guidelines, and every program implements them itself. That’s why -- works with cat but isn&#39;t guaranteed to work with every tool, and why the ./ prefix (which sidesteps option parsing entirely) is the more universal fix.</p><h3>The Shell Splits Your Words Before Any Program Reads Them</h3><p><em>Why a space is a delimiter until you say otherwise, and why quoting can’t save a filename that starts with a dash.</em></p><p>Before execve is ever called, the shell has to turn your single line of text into that argv array. The POSIX Shell Command Language defines this as a series of steps: tokenising the line, expanding variables and wildcards, <strong>field splitting</strong> on whitespace (controlled by the IFS variable), and finally <strong>quote removal</strong>.</p><p>That order explains the space problem precisely. An unquoted space is a word boundary, so field notes.txt becomes two array elements. Quotes and backslashes tell the shell &quot;don&#39;t split here&quot;, so the name reaches the program as one string.</p><p>It also explains a subtle point from the guide: quoting doesn’t fix a leading dash. Quote removal is the <em>last</em> thing the shell does, so cat &quot;-v&quot; hands cat the exact same string as cat -v. The quotes protected the text from the shell, but the program never saw them, and it applies its own option rules regardless.</p><p>So there are two separate interpreters with two separate rulebooks. The shell decides where arguments begin and end, and the program decides what each argument means. Ghost Level 1 lives in the gap between them.</p><h3>Filenames Can Be Almost Anything</h3><p><em>Why Linux allows names that break its own tools.</em></p><p>You might wonder why Linux allows a file called - at all. The answer is that, at the filesystem level, a Unix filename is almost any sequence of bytes. Only two are forbidden: the / that separates path components, and the NUL byte that ends a string in C. Spaces, leading dashes, tabs, even newlines are all legal.</p><p>POSIX does define a <em>portable</em> filename character set (letters, digits, period, underscore and hyphen) and treats names starting with a hyphen as non-portable. But “non-portable” is advice, not enforcement.</p><p>The security researcher David A. Wheeler has written about this tension since 2009 in his essay <em>Fixing Unix/Linux/POSIX Filenames</em>. His argument is that this near-total freedom causes a steady stream of bugs and security flaws, because almost no script handles every legal name correctly. His recommendations include forbidding control characters, restricting leading dashes, and prefixing wildcards with ./. He also notes that the 2024 POSIX revision chose to keep allowing newlines in names and add tooling to cope with them instead, which he considers only a partial fix.</p><p>The lesson for a beginner is simple. The filesystem lets an attacker choose almost any name, and the tools you run assume names are well-behaved. That gap is where problems appear.</p><h3>From Annoyance to Vulnerability: Argument Injection</h3><p><em>How “my cat command is hanging” turns into “someone else’s command is running my options”.</em></p><p>On Level 1 the confusion only hurt you. Now flip it around: what if <em>someone else</em> controls the filename, and <em>you</em> (or a privileged script) run the command?</p><p>In 2014, Leon Juranic of DefenseCode published <em>Back To The Future: Unix Wildcards Gone Wild</em>. The core idea is elegant and a little alarming. An unquoted wildcard like * expands to every filename in a directory, and that expansion happens <em>before</em> the program parses its options. If an attacker can create files in a directory that another user, cron job or web application later processes with a wildcard, they can plant files whose names begin with -. The program then receives those names as options. The paper showed this affecting common tools such as rm, chown, chmod, tar and rsync, ranging from changed file ownership to, in some configurations, command execution.</p><p>Nothing in that attack is a memory-corruption bug. Every program behaves exactly as documented. The weakness is purely that data (a filename) is allowed to cross into the space reserved for instructions (options).</p><p>MITRE catalogues this pattern as <strong>CWE-88: Improper Neutralization of Argument Delimiters in a Command (“Argument Injection”)</strong>. Software builds a command for another component, doesn’t properly separate its intended arguments, and so input gets read as extra options.</p><p>It isn’t only a problem in shell scripts, either. In 2017, <strong>CVE-2017–1000117</strong> showed that Git could be handed an ssh:// URL whose host part began with a dash. Git passed that &quot;hostname&quot; to the local ssh client, which read it as a command-line option instead of a host. A crafted repository URL, including one hidden in a submodule, could therefore lead to command execution on the machine running Git. Patched versions added checks that reject hosts and paths beginning with a dash.</p><p>That’s the same principle you met on Level 1, just in a different program.</p><h3>The Principle Behind Every Fix</h3><p><em>Every defence is a way of removing ambiguity about which strings are data.</em></p><p>Looked at through theory, the four fixes from the practical guide aren’t four tricks. They’re three applications of one idea:</p><ul><li><strong>Make data look unlike instructions.</strong> A ./ prefix means the argument can&#39;t start with a dash. Wheeler recommends exactly this for wildcards (./* instead of *).</li><li><strong>Mark where the instructions end.</strong> -- is the explicit delimiter from POSIX Guideline 10, and it&#39;s the standard mitigation for CWE-88 when a program supports it.</li><li><strong>Don’t hand the program a name at all.</strong> Input redirection (&lt;) makes the shell open the file, so the program never parses a filename.</li></ul><p>In application code, the same principle shows up as passing arguments as an array instead of building a shell string (for example, Python’s subprocess.run([...]) without shell=True), and as allowlisting which options a user can influence.</p><h3>Why These Concepts Matter</h3><p>Each idea in this article answers part of one question: how does a line of text become a decision about what to execute?</p><ul><li>execve and argv show that the operating system passes plain strings, and that &quot;options&quot; are a convention every program implements for itself.</li><li>The POSIX Utility Syntax Guidelines explain why the dash means “option”, why -- ends options, and why a lone - means standard input.</li><li>Shell word splitting and quote removal explain why spaces break names, and why quotes can’t rescue a leading dash.</li><li>The near-anything filename rules, and Wheeler’s critique of them, explain why attackers get to pick the strings in the first place.</li><li>Juranic’s wildcard research, CWE-88 and the 2017 Git CVE show how the same ambiguity turned into real security flaws.</li></ul><p>For a pentester, this is a mindset as much as a fact. Whenever a system takes input and later hands it to another program, ask: <em>could this data be read as an instruction?</em> That question will come up again throughout Breachlab, from shell scripts to web apps to CI/CD pipelines.</p><h3>The Breachlab Journey Continues</h3><p>This series works through Breachlab one level at a time, pairing every practical guide with a theory companion like this one. Ghost builds the Linux foundations; later tracks build on them for post-exploitation, web, cloud and Active Directory work.</p><p>Breachlab is free and runs entirely on donations. If it’s teaching you something, you can support it here: <a href="https://breachlab.org/donate">breachlab.org/donate</a></p><p>Read the practical companion to this article: <a href="https://computer-info-1.medium.com/learning-cybersecurity-from-scratch-with-breachlab-ghost-level-1-the-file-you-cant-open-by-its-0f33d05fb853?sharedUserId=computer-info-1"><strong>Learning Cybersecurity from Scratch with Breachlab — Ghost Level 1</strong></a></p><h3>Understand the concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F4c3fMuxWZf4%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D4c3fMuxWZf4&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F4c3fMuxWZf4%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/edf713d77a6aa25690937ee16fc17e4a/href">https://medium.com/media/edf713d77a6aa25690937ee16fc17e4a/href</a></iframe><h3>Watch the deep dive</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FCJ459itJPeU%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DCJ459itJPeU&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FCJ459itJPeU%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href">https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href</a></iframe><p>Follow along with the complete Breachlab playlist on YouTube: <a href="https://youtube.com/playlist?list=PLUep6spDxjvo&amp;si=ZOxoDjnnxpfAuhXW">Breachlab playlist on Assert True</a>. The channel is <a href="https://www.youtube.com/@assert_true">Assert True on YouTube</a>.</p><h3>GitHub Repository</h3><p>My notes and progress for every level live here: <a href="https://github.com/Someone-anon-coder/Breachlab">github.com/Someone-anon-coder/Breachlab</a></p><h3>Follow the complete series</h3><ul><li><a href="https://computer-info-1.medium.com/list/breachlab-ghost-i-24dad052016f">Breachlab Ghost I list on Medium</a>: every Breachlab guide and theory article in one place</li><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">Cybersecurity list on Medium</a>: my wider cybersecurity learning notes</li></ul><h3>My other learning series</h3><p><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">Arbiter (Java) Guide</a> · <a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">Arbiter (Java) Theory</a> · <a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">Python Guide</a> · <a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">Python Guide Theory</a> · <a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">GoLang Guide</a> · <a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">GoLang Guide Theory</a> · <a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">My Progress — Matplotlib</a></p><h3>What’s Next?</h3><p>In the next Breachlab Guide Theory article, we’ll explore the theory behind Ghost Level 2: why Unix hides “dotfiles” by default (a convention that famously began as a shortcut in the code of ls), what that means for where configuration and secrets end up, and why &quot;not shown&quot; is never the same as &quot;protected&quot;.</p><p>Happy hacking! 🛡️</p><h3>References</h3><ol><li>The Open Group, <em>Base Specifications Issue 7, Chapter 12: Utility Conventions (Utility Syntax Guidelines)</em>: <a href="https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/basedefs/V1_chap12.html">pubs.opengroup.org/onlinepubs/9699919799.2008edition/basedefs/V1_chap12.html</a></li><li>The Open Group, <em>Shell Command Language: Word Expansions and Quoting</em>: <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html">pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html</a></li><li>GNU Bash Reference Manual, <em>Word Splitting</em>: <a href="https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html">gnu.org/software/bash/manual/html_node/Word-Splitting.html</a></li><li>David A. Wheeler, <em>Fixing Unix/Linux/POSIX Filenames</em> (2009, updated 2024): <a href="https://dwheeler.com/essays/fixing-unix-linux-filenames">dwheeler.com/essays/fixing-unix-linux-filenames</a></li><li>David A. Wheeler, <em>Filenames and Pathnames in Shell: How to do it Correctly</em>: <a href="https://dwheeler.com/essays/filenames-in-shell.html">dwheeler.com/essays/filenames-in-shell.html</a></li><li>Leon Juranic (DefenseCode), <em>Back To The Future: Unix Wildcards Gone Wild</em> (2014): <a href="https://www.exploit-db.com/papers/33930">exploit-db.com/papers/33930</a></li><li>Help Net Security, <em>Exploiting wildcards on Linux</em> (2014): <a href="https://www.helpnetsecurity.com/2014/06/27/exploiting-wildcards-on-linux/">helpnetsecurity.com/2014/06/27/exploiting-wildcards-on-linux</a></li><li>MITRE, <em>CWE-88: Improper Neutralization of Argument Delimiters in a Command (“Argument Injection”)</em>: <a href="https://cwe.mitre.org/data/definitions/88.html">cwe.mitre.org/data/definitions/88.html</a></li><li>NSFOCUS, <em>Git SSH CVE-2017–1000117 analysis</em>: <a href="https://blog.nsfocus.net/git-ssh-cve-2017-1000117/">blog.nsfocus.net/git-ssh-cve-2017–1000117</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee9376b6a071" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/understanding-the-linux-command-line-the-theory-behind-options-word-splitting-and-how-a-ee9376b6a071">Understanding the Linux Command Line — The Theory Behind Options, Word Splitting, and How a…</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Cybersecurity from Scratch with Breachlab — Ghost Level 1: The File You Can’t Open by Its…]]></title>
            <link>https://infosecwriteups.com/learning-cybersecurity-from-scratch-with-breachlab-ghost-level-1-the-file-you-cant-open-by-its-0f33d05fb853?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/0f33d05fb853</guid>
            <category><![CDATA[command-line]]></category>
            <category><![CDATA[penetration-testing]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[ethical-hacking]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 07:46:38 GMT</pubDate>
            <atom:updated>2026-11-02T13:36:39.882Z</atom:updated>
            <content:encoded><![CDATA[<h3>Learning Cybersecurity from Scratch with Breachlab — Ghost Level 1: The File You Can’t Open by Its Name</h3><h4>Day 2 of my Breachlab Learning Journey | Why a filename that starts with a dash or contains a space breaks a perfectly normal command, and four ways to tell the shell exactly what you mean</h4><figure><img alt="Dark Linux terminal at night showing a file listing, with one highlighted filename the command mistakes for an option" src="https://cdn-images-1.medium.com/max/1024/1*ZxvhAxyfNjXYJnH3D1mgTQ.png" /><figcaption>Ghost Level 1: the file is right there in the listing. The command just won’t see it</figcaption></figure><p><em>Disclaimer: This series is for educational purposes only. Everything here happens on Breachlab’s own practice targets, which exist to be attacked legally. Don’t try these techniques on systems you don’t own or don’t have written permission to test. Following Breachlab’s guidelines, I won’t share passwords, flags or a copy-paste solution. Every example below runs on a small practice folder you build yourself, so you learn the technique and still solve the real level on your own.</em></p><p>You log in. You run ls. The file you need is sitting right there, plainly listed.</p><p>You type cat and the filename, hit Enter, and... nothing. The cursor just blinks at you. Or you get an error saying the file doesn&#39;t exist, about a file you can literally see.</p><p>That was my first ten minutes on Ghost Level 1, and honestly it was a more useful lesson than any of the “find the hidden thing” levels. Nothing is hidden here. Nothing is locked. The only thing standing between you and the password is how your shell reads what you typed.</p><p>This is Day 2 of my Breachlab Learning Journey, where I’m working through Breachlab’s tracks level by level and documenting what each one actually teaches. On Day 1 (Ghost Level 0), the job was getting connected over SSH and learning to look around a box before touching anything. Today builds directly on that: you know how to find files, and now you find out that <em>finding</em> a file and <em>naming</em> it correctly are two different skills.</p><h3>Today’s Topics</h3><ul><li>Why some filenames get mistaken for command options</li><li>Why a space in a filename quietly turns one argument into two</li><li>Four reliable ways to refer to an awkward file</li><li>A habit for spotting trouble before you hit Enter</li></ul><h3>First, Build a Practice Folder</h3><p>I’m not going to walk you through the real level, so let’s build something with the same kinds of problems. On any Linux machine or VM, run this:</p><pre>mkdir -p ~/lab/ghost1 &amp;&amp; cd ~/lab/ghost1<br><br>echo &quot;practice value one&quot;   &gt; ./-<br>echo &quot;practice value two&quot;   &gt; ./-v<br>echo &quot;practice value three&quot; &gt; &quot;./field notes.txt&quot;<br>ls -la</pre><p>You now have three files whose names are deliberately annoying: one called just -, one called -v, and one with a space in it. The real level uses its own filename and its own twist, so treat these as training weights, not the answer.</p><h4>Problem 1: The Filename That Looks Like an Option</h4><p>Try reading the second file the obvious way:</p><pre>cat -v</pre><p>Your terminal just sits there. It hasn’t frozen; cat is waiting for you to type something. Press Ctrl + C to get out.</p><p>Here’s what happened. Almost every Linux command treats an argument starting with - as an <em>option</em>, a switch that changes how the command behaves. -v happens to be a real option for cat (&quot;show non-printing characters&quot;). So cat read your filename, decided it was a setting, found no actual file to read, and fell back to reading from your keyboard.</p><p>The lone - file trips you up in a similar way. By long-standing Unix convention, a single - on its own means &quot;standard input&quot;: read from the keyboard or a pipe instead of a file. So cat - doesn&#39;t open the file named - either. It waits for you to type.</p><p>The file isn’t missing. The command just never looked for it, because it understood your filename as an instruction.</p><h4>Problem 2: The Filename With a Space</h4><p>Now the third file:</p><pre>cat field notes.txt</pre><p>This time you get two errors, one for field and one for notes.txt. Before cat even runs, your shell splits what you typed on spaces to decide where each argument starts and ends. So cat was handed two separate names, and neither exists.</p><p>The space is perfectly legal in a filename. Linux allows almost any character in one, but the shell has its own rules about what a space <em>means</em> on a command line, and those rules win unless you override them.</p><figure><img alt="Diagram showing a dash-prefixed filename routed to options, a filename with a space split in two, and four fixes below." src="https://cdn-images-1.medium.com/max/1024/1*zFxApe-Z7dSA5tUGVBroZg.png" /><figcaption>A leading dash turns a filename into an option, and a space splits one name into two. Four fixes remove the ambiguity.</figcaption></figure><h3>Four Ways to Tell the Shell What You Mean</h3><p>Every one of these fixes does the same thing in a different way: it removes the ambiguity, so the command can only read your argument as a filename.</p><h4>1. Give it a path</h4><pre>cat ./-v<br>cat ./-</pre><p>./ means &quot;in the current directory.&quot; The argument now starts with a dot, not a dash, so no command will ever mistake it for an option. This is the fix I reach for first because it works everywhere, even with older or unusual tools.</p><h4>2. Use the double dash</h4><pre>cat -- -v</pre><p>A bare -- is a widely followed convention that means &quot;options end here; everything after this is a filename.&quot; Most standard Linux tools honor it. It&#39;s very handy in scripts, but not every program supports it, so test it before you rely on it.</p><p>One catch: cat -- - still reads from standard input, because many tools treat a lone - as &quot;stdin&quot; even after --. For that particular name, the path fix is the reliable one.</p><h4>3. Quote or escape it</h4><pre>cat &quot;field notes.txt&quot;<br>cat field\ notes.txt</pre><p>Quotes tell the shell “this whole thing is one argument.” A backslash does the same for a single character, so \ means &quot;a literal space, don&#39;t split here.&quot; Quoting fixes the space problem, but <strong>not</strong> the dash problem: cat &quot;-v&quot; still gets read as an option, because the quotes are removed before cat ever sees the text.</p><h4>4. Redirect it in</h4><pre>cat &lt; -</pre><p>With &lt;, the <em>shell</em> opens the file and feeds its contents to the command. The command never sees a filename at all, so it has nothing to misread. It&#39;s a neat trick, and it&#39;s useful to understand even when you don&#39;t need it.</p><h3>A Habit Worth Building: Look Before You Type</h3><p>Most of the pain on this level comes from assuming a filename is “normal.” Two small checks help you catch the weird ones before you hit Enter:</p><pre>ls -Q        # wraps every name in quotes, so spaces and odd characters stand out<br>ls -b        # shows escapes for special characters, like field\ notes.txt<br>file ./*     # tells you what each file actually contains</pre><p>And use <strong>Tab completion</strong>. Type the first character or two and press Tab; the shell fills in the name <em>and</em> adds whatever escaping it needs. Half the time, the shell is quietly showing you the answer to &quot;how do I type this name safely?&quot;</p><p>When you try the actual level, slow down at the ls step. Look at exactly what the filename is, work out which problem it causes (option? standard input? splitting? more than one?), then pick the fix that removes that ambiguity. That&#39;s the entire skill, and it&#39;s yours to apply.</p><h3>Key Takeaway</h3><p>A command line isn’t just text. It’s text the shell splits into arguments, which the command then sorts into options and files. Ghost Level 1 lives in the gap between those two steps. A leading dash makes a filename look like an option, a lone dash means standard input, and a space splits one name into two. Every fix (a ./ path, --, quotes or escapes, or &lt; redirection) works by taking away the command&#39;s room to guess.</p><p>This matters well beyond one wargame level. When attacker-controlled filenames reach scripts that don’t handle them carefully, this exact confusion stops being a nuisance and becomes a vulnerability. I dig into that history, and the standards that explain <em>why</em> Linux behaves like this, in today’s theory companion:</p><p><a href="https://medium.com/@computer-info-1/understanding-the-linux-command-line-the-theory-behind-options-word-splitting-and-how-a-ee9376b6a071"><strong>Understanding Linux Command-Line Parsing — The Theory Behind Ghost Level 1</strong></a></p><h3>The Breachlab Journey Continues</h3><p>One level at a time, this series works through Breachlab’s tracks, from Ghost’s Linux fundamentals toward post-exploitation, web, cloud and Active Directory. Each level gets a practical guide like this one and a theory companion explaining the “why.”</p><p>Breachlab is free and runs entirely on donations. If it’s teaching you something, you can support it here: <a href="https://breachlab.org/donate">breachlab.org/donate</a></p><h3>Watch the concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F4c3fMuxWZf4%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D4c3fMuxWZf4&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F4c3fMuxWZf4%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/edf713d77a6aa25690937ee16fc17e4a/href">https://medium.com/media/edf713d77a6aa25690937ee16fc17e4a/href</a></iframe><h3>Watch the deep dive</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FCJ459itJPeU%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DCJ459itJPeU&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FCJ459itJPeU%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href">https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href</a></iframe><p>Follow along with the complete Breachlab playlist on YouTube: <a href="https://youtube.com/playlist?list=PLUep6spDxjvo&amp;si=ZOxoDjnnxpfAuhXW">Breachlab playlist on Assert True</a>. The channel is <a href="https://www.youtube.com/@assert_true">Assert True on YouTube</a>.</p><h3>GitHub Repository</h3><p>My notes and progress for every level live here: <a href="https://github.com/Someone-anon-coder/Breachlab">github.com/Someone-anon-coder/Breachlab</a></p><h3>Follow the complete series</h3><ul><li><a href="https://computer-info-1.medium.com/list/breachlab-ghost-i-24dad052016f">Breachlab Ghost I list on Medium</a>: every Breachlab guide and theory article in one place</li><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">Cybersecurity list on Medium</a>: my wider cybersecurity learning notes</li></ul><h3>My other learning series</h3><p><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">Arbiter (Java) Guide</a> · <a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">Arbiter (Java) Theory</a> · <a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">Python Guide</a> · <a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">Python Guide Theory</a> · <a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">GoLang Guide</a> · <a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">GoLang Guide Theory</a> · <a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">My Progress — Matplotlib</a></p><h3>What’s Next?</h3><p>On Day 3, we move to Ghost Level 2: files that don’t show up in a normal listing at all, why Linux hides some names by default, and how to stop trusting the first ls you run.</p><p>Happy hacking! 🛡️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0f33d05fb853" width="1" height="1" alt=""><hr><p><a href="https://infosecwriteups.com/learning-cybersecurity-from-scratch-with-breachlab-ghost-level-1-the-file-you-cant-open-by-its-0f33d05fb853">Learning Cybersecurity from Scratch with Breachlab — Ghost Level 1: The File You Can’t Open by Its…</a> was originally published in <a href="https://infosecwriteups.com">InfoSec Write-ups</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Linux Enumeration and Access Control: What Happens in the First Sixty Seconds on a Target]]></title>
            <link>https://infosecwriteups.com/linux-enumeration-and-access-control-what-happens-in-the-first-sixty-seconds-on-a-target-ba118b6ef3e0?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba118b6ef3e0</guid>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[ethical-hacking]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 08:28:49 GMT</pubDate>
            <atom:updated>2026-10-28T12:09:40.634Z</atom:updated>
            <content:encoded><![CDATA[<h4>Level 0 of Ghost I | How identity is checked, why permissions are nine bits, and how credentials leak into files</h4><figure><img alt="A glowing CRT monitor showing an authentic Linux shell session, directory hierarchy trees, and access control matrix overlays in emerald and deep navy." src="https://cdn-images-1.medium.com/max/1024/1*GkT8kKgLgfJaC2-PMPkRqg.jpeg" /><figcaption><em>The first sixty seconds on an unknown target decide whether the engagement is methodical or random.</em></figcaption></figure><p><em>This article was prepared with AI assistance. The companion video’s narration is synthesized speech and the presenter is an animated illustration, not a real person. This guide teaches methodology and intentionally does not reveal wargame solutions or passwords.</em></p><blockquote><strong><em>LAB ENVIRONMENT — commands executed in a local throwaway sandbox</em></strong></blockquote><pre># Throwaway reproduction sandbox (runs locally as unprivileged user; display_user=&quot;dev&quot;)<br>mkdir -p ~/lab/{reports,scripts,.config}<br>cd ~/lab<br>echo &quot;README content&quot; &gt; README.txt<br>echo &quot;status ok&quot; &gt; reports/q3.txt<br>echo &quot;API_TOKEN=example-not-a-real-token&quot; &gt; deploy.env<br>chmod 640 deploy.env<br>echo &quot;rotate the deploy token before friday&quot; &gt; .reminder<br>chmod 600 .reminder<br>printf &quot;todo: archive old reports\r\ntodo: rotate keys\r\n&quot; &gt; notes.txt<br>printf &quot;\x00\x01\x02\xfe\xff\x00\x00\x00CONNECT db.internal:5432\x00\xde\xad\xbe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00user=deploy\x00&quot; &gt; data.bin<br>chmod 755 scripts</pre><h3>What this level is</h3><p>You land on a shell on a machine you have never seen. There is no map, no architecture diagram, and no administrator telling you what matters. What you do in the next sixty seconds determines whether the rest of the session is disciplined, repeatable, and effective or chaotic and noisy. Professional operators on both offensive engagements and incident response investigations begin with the same three fundamental questions: who am I, where am I, and what is here?</p><p>In this guide, we answer those questions against BreachLab’s Ghost track, Level 0, titled “First Contact”. The level page lists three foundational commands: ls, cd, and cat.</p><p>Behind those three simple commands lie decades of computer science history and operating system architecture. In this guide, we examine:</p><ol><li>Why identity in Unix is governed by kernel-level numeric IDs rather than environment variables.</li><li>How the filesystem hierarchy and hidden dot-files originated from a historical shortcut.</li><li>How the access matrix compresses into nine permission bits, and how discretionary access control creates predictable security vulnerabilities.</li></ol><ul><li><strong>Platform:</strong> <a href="https://breachlab.org">BreachLab</a>, a free, legal hands-on platform where every level is an isolated, real Linux environment.</li><li><strong>Track &amp; Level:</strong> Ghost I · Level 0 — First Contact (100 points).</li><li><strong>Target Connection:</strong> ssh ghost0@204.168.229.209 -p 2222</li><li><strong>Commands Covered:</strong> whoami, pwd, ls, cd, cat, file, head, tail, strings, xxd, stat, type.</li><li><strong>Level Objective:</strong> Locate the credentials for the next account (ghost1).</li></ul><p><strong>Watch the full long-form video walkthrough:</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FCJ459itJPeU%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DCJ459itJPeU&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FCJ459itJPeU%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href">https://medium.com/media/08ec7de4649098e348c8551f48f8c835/href</a></iframe><p><strong>Follow along with the complete series playlist:</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLUep6spDxjvo%26si%3DuwtryYSM125deaiu&amp;type=text%2Fhtml&amp;schema=youtube&amp;display_name=YouTube&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLUep6spDxjvo" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9346f4e1dfbe95740f629d65a4db3b24/href">https://medium.com/media/9346f4e1dfbe95740f629d65a4db3b24/href</a></iframe><p><em>Compliance Note: In strict adherence to BreachLab creator rules, this article teaches the underlying method, mechanics, and security theory. No passwords, flags, or copy-paste solutions are published.</em></p><h3>Today’s Topics</h3><ul><li><strong>The Orientation Loop</strong>: Executing the initial sixty-second triage using whoami, pwd, and ls -la.</li><li><strong>SSH Protocol Mechanics</strong>: Understanding TCP handshakes, Diffie-Hellman key agreement, and host verification.</li><li><strong>Identity and Complete Mediation</strong>: Why the kernel evaluates effective UIDs rather than shell variables.</li><li><strong>Filesystem Enumeration</strong>: Walking directory tables, understanding inodes, and unmasking dot-files.</li><li><strong>Permission Bit Triads</strong>: Decoding octal modes, directory traversal mechanics, and access matrix theory.</li><li><strong>Path Traversal with Shell Builtins</strong>: Why cd must execute inside the parent shell process.</li><li><strong>Defensive Content Inspection</strong>: Safely examining unknown streams using file, cat -A, and strings.</li><li><strong>Unsecured Credentials in Files (T1552.001)</strong>: Real-world impact, kill chain positioning, and honeytoken detection.</li></ul><h3>Orienting on an Unknown Target: whoami and pwd</h3><p>When I first connect to a remote target, before typing any complex commands or running automated scripts, I establish orientation. I need to know the security context of the process running my shell and my exact position in the directory hierarchy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tdmqBIVZI5mHJ_ITImueGA.jpeg" /><figcaption><em>The opening moments on an unknown shell determine the course of the investigation.</em></figcaption></figure><p>I begin by asking the kernel for my active identity. I run whoami:</p><pre>$ whoami<br>dev</pre><p>The command returns dev (or ghost0 when logged into the live target). Behind this single word, the whoami utility executed the geteuid() system call to fetch the numeric effective user ID from the operating system kernel, then performed a lookup against /etc/passwd.</p><p>To illustrate why this matters, consider what happens if I rely on the common $USER environment variable instead:</p><pre>$ echo $USER<br>dev<br>$ USER=root<br>$ echo $USER<br>root<br>$ whoami<br>dev</pre><p>The environment variable $USER is completely under user-space control. An adversary or a misconfigured script can alter it at will. The kernel&#39;s reference monitor completely ignores $USER when enforcing access permissions; it consults only the numeric UID bound to the process structure in memory.</p><p>Next, I check my location in the filesystem with pwd:</p><pre>$ pwd<br>/home/dev/lab</pre><p>The pwd (print working directory) command prints the absolute path of the current directory. This path serves as the anchor point for every relative path resolution performed by the kernel during system calls like open() and execve().</p><p><em>Takeaway: Always query the kernel directly for process identity rather than trusting environment variables that user space can manipulate.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oP86Jt4SR-UKmnSK9L6OTw.jpeg" /><figcaption><em>BreachLab provides authentic Linux environments over SSH.</em></figcaption></figure><h3>Anatomy of an SSH Connection</h3><p>To reach Level 0, we initiate an SSH connection to port 2222:</p><pre>$ ssh ghost0@204.168.229.209 -p 2222</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GnzzBoQ1dav0vs-_H4PfSQ.jpeg" /><figcaption><em>Breakdown of an SSH connection string: binary, target account, host address, and non-standard port</em></figcaption></figure><p>This command initiates a multi-stage protocol exchange:</p><ol><li><strong>Transport Layer Handshake</strong>: The client opens a TCP connection to port 2222. Port 2222 is chosen by convention for secondary or containerised SSH daemons, avoiding privileged port binding (ports below 1024 require root privileges).</li><li><strong>Version Exchange</strong>: Both client and server exchange identification strings (e.g., SSH-2.0-OpenSSH_8.9p1).</li><li><strong>Key Exchange (KEX)</strong>: Both sides agree on cryptographic algorithms and execute a Diffie-Hellman or elliptic curve exchange to establish a shared session key without transmitting it over the wire.</li><li><strong>Server Authentication</strong>: The server signs the exchange with its private host key. The client verifies this signature against its local ~/.ssh/known_hosts file.</li><li><strong>User Authentication</strong>: Once encryption is established, the client sends user authentication credentials (in Level 0, password authentication; in hardened environments, Ed25519 public keys or certificates).</li></ol><p><em>Takeaway: SSH ensures confidentiality, integrity, and server authentication before a single credential character is sent across the network.</em></p><h3>Enumerating the Filesystem: ls and What Else You Could Use</h3><p>With identity and working directory confirmed, the next priority is inventorying what is present.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4DcSQaQisznu8KXtOhv_IA.jpeg" /><figcaption><em>The orientation triage answering who am I, where am I, and what is here.</em></figcaption></figure><p>I start with a plain ls:</p><pre>$ ls<br>README.txt<br>data.bin<br>deploy.env<br>notes.txt<br>reports<br>scripts</pre><p>A standard ls reads the directory data structure and outputs filenames alphabetically. However, a plain listing hides crucial information. In Unix, files whose names begin with a period (.) are excluded from default directory listings.</p><p>To see all entries, I supply the -a flag:</p><pre>$ ls -a<br>.<br>..<br>.config<br>.reminder<br>README.txt<br>data.bin<br>deploy.env<br>notes.txt<br>reports<br>scripts</pre><p>Notice what appeared: . (the current directory), .. (the parent directory), .config (a configuration directory), and .reminder (a hidden text file).</p><p>In security assessments, dot-files are prime locations for misconfigured credentials, shell histories (.bash_history), cloud tokens (.aws/), and private keys (.ssh/id_rsa).</p><p>To inspect file metadata, I combine flags into the standard enumeration command ls -la:</p><pre>$ ls -la<br>total 40<br>drwxrwxr-x 5 dev dev 4096 Oct  7 07:02 .<br>drwxrwxr-x 3 dev dev 4096 Oct  7 07:02 ..<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 .config<br>-rw------- 1 dev dev   38 Oct  4 07:02 .reminder<br>-rw-r--r-- 1 dev dev   80 Oct  7 07:02 README.txt<br>-rw-r--r-- 1 dev dev   61 Sep  7 07:02 data.bin<br>-rw-r----- 1 dev dev   67 Oct  2 07:02 deploy.env<br>-rw-r--r-- 1 dev dev   46 Sep 28 07:02 notes.txt<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 reports<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 scripts</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2PQ4sprli8ds-R2C7Y54Hg.jpeg" /><figcaption><em>Examining file metadata, hidden dot-entries, and permission triads with </em><em>ls -la.</em></figcaption></figure><h3>What if ls is not available?</h3><p>In minimal Docker containers or restricted shells, the ls binary may be absent or restricted. Because filename expansion (globbing) is handled directly by the shell before command execution, we can enumerate files using built-in shell features:</p><pre>$ echo *<br>README.txt data.bin deploy.env notes.txt reports scripts</pre><p>When recursive enumeration is needed, find and tree provide structured visibility:</p><pre>$ tree -a -L 2<br>.<br>├── .config<br>│   └── app.conf<br>├── .reminder<br>├── README.txt<br>├── data.bin<br>├── deploy.env<br>├── notes.txt<br>├── reports<br>│   ├── q3.txt<br>│   └── q4.txt<br>└── scripts<br>    └── backup.sh<br><br>4 directories, 9 files</pre><p><em>Takeaway: Plain </em><em>ls conceals administrative configuration; standard enumeration requires </em><em>ls -la or native shell globbing.</em></p><h3>Decoding Access Rights: Reading a Permission String</h3><p>The output of ls -la presents a 10-character string at the beginning of each line. Understanding this string is essential for recognizing privilege boundaries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DsO1VC-TgXV-qwlO1roaRA.jpeg" /><figcaption><em>Anatomy of a Unix permission string and octal conversion.</em></figcaption></figure><p>Consider the line for deploy.env: -rw-r----- 1 dev dev 67 Oct 2 07:02 deploy.env</p><p>We dissect the string from left to right:</p><ul><li><strong>Character 1 (File Type)</strong>: - denotes a regular file. A d indicates a directory, while l denotes a symbolic link.</li><li><strong>Characters 2–4 (Owner Permissions)</strong>: rw- indicates the owner (dev) has read (r=4) and write (w=2) permissions, but no execute (x=1). Total = 6.</li><li><strong>Characters 5–7 (Group Permissions)</strong>: r-- indicates members of the owning group (dev) have read permission only. Total = 4.</li><li><strong>Characters 8–10 (Others Permissions)</strong>: --- indicates any user who is neither the owner nor a member of the group has zero permissions. Total = 0.</li></ul><p>The combined octal mode is 640.</p><p>We can verify this numeric representation directly using stat:</p><pre>$ stat -c &#39;%A  %a  %U:%G  %s bytes  %n&#39; README.txt .reminder scripts/backup.sh deploy.env<br>-rw-r--r--  644  dev:dev  80 bytes  README.txt<br>-rw-------  600  dev:dev  38 bytes  .reminder<br>-rwxr-xr-x  755  dev:dev  40 bytes  scripts/backup.sh<br>-rw-r-----  640  dev:dev  67 bytes  deploy.env</pre><h3>The Crucial Difference for Directories</h3><p>For files, r allows reading content, w allows modifying bytes, and x allows running the file as an executable program.</p><p>For directories, the bits have distinct meanings:</p><ul><li>r (Read): Grants the ability to list the names of files inside the directory (e.g., with ls).</li><li>w (Write): Grants the ability to create, delete, or rename files within the directory (contingent on execute permissions).</li><li>x (Execute/Search): Grants the ability to enter the directory (cd) and access the inodes of files inside it.</li></ul><p><em>Takeaway: Without directory execute permissions, read permissions allow listing names but prevent accessing file contents or metadata.</em></p><h3>Navigating the Hierarchy: Moving Around with cd</h3><p>Navigating the directory tree is accomplished using cd (change directory).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BHKEgbSkVOqIJhH6ZENn0Q.jpeg" /><figcaption><em>Inspecting command implementations with </em><em>type: builtins versus disk executables.</em></figcaption></figure><p>When we check the binary location of cd, we encounter a fundamental architectural distinction:</p><pre>$ type cd ls cat pwd<br>cd is a shell builtin<br>ls is /usr/bin/ls<br>cat is /usr/bin/cat<br>pwd is a shell builtin</pre><p>ls and cat are independent executable binaries located in /usr/bin/. When invoked, the shell calls fork() to create a child process, followed by execve() to load the binary into memory.</p><p>cd, however, is a <strong>shell builtin</strong>. In Unix process architecture, a child process inherits a copy of its parent&#39;s working directory, but any modifications made by the child to its own working directory do not propagate back to the parent. If cd were an external executable binary, running /usr/bin/cd reports would change the directory of the child process, which would then immediately exit, leaving the user&#39;s interactive shell exactly where it started.</p><h3>Common Navigation Patterns</h3><p>We test navigation shortcuts across our lab hierarchy:</p><pre>$ cd reports &amp;&amp; pwd &amp;&amp; cd .. &amp;&amp; pwd &amp;&amp; cd ~ &amp;&amp; pwd &amp;&amp; cd - &amp;&amp; pwd<br>/home/dev/lab/reports<br>/home/dev/lab<br>/home/dev<br>/home/dev/lab<br>/home/dev/lab</pre><p>Key shortcuts:</p><ul><li>..: References the parent directory entry.</li><li>~: Expands to the current user&#39;s home directory ($HOME).</li><li>-: Returns to the previous working directory ($OLDPWD).</li></ul><p><em>Takeaway: </em><em>cd must be implemented inside the shell process because child processes cannot alter parent process state.</em></p><h3>Inspecting Files Safely: cat and the Tools Around It</h3><p>Once a target file is discovered, we must inspect its contents. The standard utility is cat (short for concatenate).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eM17wHohyHWxx7vsNJsorA.jpeg" /><figcaption><em>Examining files safely: content inspection, line terminators, and binary strings.</em></figcaption></figure><p>cat reads file streams sequentially and writes them to standard output. While effective for short text files, dumping arbitrary files directly with cat introduces risks:</p><ol><li>Long files scroll off the screen, obscuring terminal history.</li><li>Binary files send control characters that can corrupt terminal settings or reprogram keyboard mappings.</li></ol><p>To avoid this, we follow a two-step rule: <strong>identify first, read second</strong>.</p><p>We inspect file types using file:</p><pre>$ file *<br>README.txt: ASCII text<br>data.bin:   data<br>deploy.env: ASCII text<br>notes.txt:  ASCII text, with CRLF line terminators<br>reports:    directory<br>scripts:    directory</pre><p>Notice notes.txt: it is flagged as containing <strong>CRLF line terminators</strong> (Windows-style \r\n).</p><p>If we view notes.txt with plain cat, the carriage return is invisible. But when we pass -A (show all invisible characters), the hidden control character becomes evident:</p><pre>$ cat -A notes.txt<br>todo: archive old reports^M$<br>todo: rotate keys^M$</pre><p>The ^M represents carriage return (\r, ASCII 0x0D), while $ marks the newline (\n, ASCII 0x0A). If an automated script parses this file to extract a password or hostname, the carriage return remains attached to the string, causing subsequent authentication requests to fail silently.</p><h3>Specialized Inspection Tools</h3><p>When dealing with structured, binary, or large datasets, we use tailored utilities:</p><p><strong>1. Inspecting Binaries with strings</strong> Instead of dumping raw bytes from data.bin, strings extracts contiguous printable ASCII sequences (minimum length 4 by default):</p><pre>$ strings data.bin<br>CONNECT db.internal:5432<br>user=deploy</pre><p><strong>2. Exact Byte Inspection with xxd</strong> To view hexadecimal representations and offsets:</p><pre>$ xxd data.bin<br>00000000: 0001 02fe ff00 0000 434f 4e4e 4543 5420  ........CONNECT <br>00000010: 6462 2e69 6e74 6572 6e61 6c3a 3534 3332  db.internal:5432<br>00000020: 00de adbe ef00 0000 0000 0000 0000 0000  ................<br>00000030: 0075 7365 723d 6465 706c 6f79 00         .user=deploy.</pre><p><strong>3. Bounded Inspection with head and tail</strong> To read headers or trailing log entries without flooding standard output:</p><pre>$ head -n 3 reports/q3.txt<br>line 001: status ok<br>line 002: status ok<br>line 003: status ok</pre><p><strong>4. Recursive Content Searching with grep</strong> When searching across an entire hierarchy for credentials:</p><pre>$ grep -rn -i &#39;token&#39; . 2&gt;/dev/null<br>./.reminder:1:rotate the deploy token before friday<br>./deploy.env:2:API_TOKEN=example-not-a-real-token</pre><p><em>Takeaway: Always classify unknown files with </em><em>file before reading, and use </em><em>cat -A to catch invisible carriage returns that break scripts.</em></p><h3>Solving Level 0 on the Target Machine</h3><p>Now we apply our orientation methodology to the live BreachLab Ghost 0 target.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n8F5cLQAImxXofm9iIEhTw.jpeg" /><figcaption><em>Step-by-step navigation through the target directory structure to locate the level credential.</em></figcaption></figure><p>We log in over SSH as ghost0. The session opens in /home/ghost0. We execute our triage loop:</p><pre>$ whoami<br>ghost0<br>$ pwd<br>/home/ghost0<br>$ ls<br>workspace</pre><p>The directory contains an entry named workspace. We move into it:</p><pre>$ cd workspace<br>$ ls<br>archive</pre><p>Inside workspace is a directory named archive. In enterprise systems, archive directories are frequently overlooked during credential rotation sweeps. We descend into archive:</p><pre>$ cd archive<br>$ ls<br>credentials</pre><p>We find a file named credentials. Before reading it, we verify its format:</p><pre>$ file credentials<br>credentials: ASCII text</pre><p>Finally, we display the credential:</p><pre>$ cat credentials</pre><p><em>(The password output is intentionally redacted in accordance with BreachLab platform rules).</em></p><p>The password string displayed in this file represents the flag for Level 0 and serves as the SSH authentication password for ghost1. We immediately log this credential in an external secure password manager before proceeding to Level 1.</p><p><em>Takeaway: Level 0 requires zero exploit payloads; disciplined filesystem traversal and directory enumeration locate the credential directly.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wHJ_FUb_tw_hqqSR_gl1Ew.jpeg" /><figcaption><em>Complete mediation ensures that every system call requesting file access is validated against kernel process credentials</em></figcaption></figure><h3>Running It</h3><p>Here is the complete orientation session as executed in our sandbox environment, demonstrating the unbroken workflow from initial connection to secret identification:</p><pre>$ whoami<br>dev<br>$ pwd<br>/home/dev/lab<br>$ ls -la<br>total 40<br>drwxrwxr-x 5 dev dev 4096 Oct  7 07:02 .<br>drwxrwxr-x 3 dev dev 4096 Oct  7 07:02 ..<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 .config<br>-rw------- 1 dev dev   38 Oct  4 07:02 .reminder<br>-rw-r--r-- 1 dev dev   80 Oct  7 07:02 README.txt<br>-rw-r--r-- 1 dev dev   61 Sep  7 07:02 data.bin<br>-rw-r----- 1 dev dev   67 Oct  2 07:02 deploy.env<br>-rw-r--r-- 1 dev dev   46 Sep 28 07:02 notes.txt<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 reports<br>drwxr-xr-x 2 dev dev 4096 Oct  7 07:02 scripts<br>$ stat -c &#39;%A  %a  %U:%G  %s bytes  %n&#39; deploy.env .reminder<br>-rw-r-----  640  dev:dev  67 bytes  deploy.env<br>-rw-------  600  dev:dev  38 bytes  .reminder<br>$ file *<br>README.txt: ASCII text<br>data.bin:   data<br>deploy.env: ASCII text<br>notes.txt:  ASCII text, with CRLF line terminators<br>reports:    directory<br>scripts:    directory<br>$ cat -A notes.txt<br>todo: archive old reports^M$<br>todo: rotate keys^M$<br>$ grep -rn -i &#39;token&#39; . 2&gt;/dev/null<br>./.reminder:1:rotate the deploy token before friday<br>./deploy.env:2:API_TOKEN=example-not-a-real-token</pre><h3>Reading it in groups:</h3><ul><li><strong>Identity &amp; Context</strong>: whoami and pwd verify unprivileged user execution inside the lab root.</li><li><strong>Surface Discovery</strong>: ls -la unmasks hidden entries (.config, .reminder) that default listings omit.</li><li><strong>Permission Assessment</strong>: stat reveals that .reminder is restricted to mode 600 (owner read/write only), while deploy.env is mode 640.</li><li><strong>Stream Verification</strong>: file alerts us to Windows-style CRLF terminators in notes.txt, confirmed via cat -A.</li><li><strong>Targeted Extraction</strong>: grep recursively isolates credential assignments without manual file crawling.</li></ul><h3>Theory: Why Unix Looks the Way It Does</h3><p><em>Fifty years of hierarchical abstraction and the accident of dot-files.</em></p><h3>Sources</h3><ul><li>Ritchie &amp; Thompson, <em>The UNIX Time-Sharing System</em> (Communications of the ACM, 1974)</li><li><em>Unix Programmer’s Manual</em>, 1st Edition (Bell Laboratories, 1971)</li><li>Filesystem Hierarchy Standard (FHS 3.0, Linux Foundation, 2015)</li></ul><p>In 1969 at Bell Laboratories, Ken Thompson and Dennis Ritchie began designing a streamlined operating system on a discarded Digital Equipment Corporation PDP-7. The design principles they established remain the structural foundation of modern Linux systems.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SdbTMPcll1EsuAhouJfB7A.jpeg" /><figcaption><em>The evolution of the Unix directory tree from the 1971 Programmer’s Manual to modern FHS.</em></figcaption></figure><p>Ritchie and Thompson formulated four enduring architectural tenets:</p><ol><li><strong>Hierarchical Namespace</strong>: A single rooted tree where directories contain file descriptors and pointers to subordinate directories.</li><li><strong>Unstructured Byte Streams</strong>: A file is simply an arbitrary sequence of bytes. The operating system imposes no record delimiters or indexed access methods; interpretation is delegated entirely to user space.</li><li><strong>Pervasive File Abstraction</strong>: Peripheral devices, storage disks, terminals (/dev/tty), and inter-process communication channels are presented as file descriptors within the namespace.</li><li><strong>Tool Composition</strong>: Modular utilities that perform a single task well, linked via unidirectional pipes (introduced by Douglas McIlroy in 1973).</li></ol><p>In November 1971, the <em>Unix Programmer’s Manual</em> (First Edition) formally documented ls, cat, and chdir (the predecessor to cd). In the 1974 CACM paper, directories were defined as simple lookup tables mapping character string names to numeric internal indices called <strong>i-numbers</strong> (inodes).</p><h3>The Accident of Dot-Files</h3><p>The convention of “hidden” files originated as a programming shortcut rather than a deliberate security mechanism. According to a widely cited historical account by Rob Pike, when Thompson and Ritchie added directory navigation, every directory received two structural entries: . (pointing to the directory itself) and .. (pointing to its parent). To prevent these two entries from cluttering directory listings, a check was added to ls: if the first character of a filename was ., the entry was skipped.</p><p>This simple prefix check inadvertently caused <em>any</em> file beginning with a period to be hidden from the default listing. Developers quickly adopted this behavior to stash user configuration files (.profile, .bashrc) out of everyday view. In modern security testing, this design artifact makes dot-files the primary repository for unmonitored credentials.</p><h3>Under the Hood: Directory Inodes</h3><p>In modern Linux filesystems (ext4, XFS), a directory is an inode containing data blocks filled with directory entries (struct linux_dirent64):</p><pre>$ ls -i<br>5771093 README.txt<br>5771103 data.bin<br>5771104 deploy.env<br>5771095 notes.txt<br>5771100 reports<br>5771098 scripts</pre><p>The filename is merely a human-readable link pointing to an inode number. The permissions, file size, timestamps, and data block pointers reside entirely inside the inode table, not within the directory entry itself.</p><h3>Theory: Why SSH Exists (History, Diffie-Hellman, and MITM)</h3><p><em>From plaintext cleartext telnet to cryptographically authenticated sessions.</em></p><h4>Sources</h4><ul><li>Ylönen, <em>SSH — Secure Shell Remote Login</em> (1995)</li><li>RFC 4251: <em>The Secure Shell (SSH) Protocol Architecture</em> (IETF, 2006)</li><li>Diffie &amp; Hellman, <em>New Directions in Cryptography</em> (IEEE Transactions on Information Theory, 1976)</li></ul><p>Before SSH, remote administration relied on Telnet (RFC 854) and the Berkeley r-commands (rlogin, rsh). These protocols transmitted every packet across local networks and the ARPANET in plaintext. Anyone with network access could capture passwords and keystrokes using packet sniffers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VUHbyR900cu9tqA2N8d_Ig.jpeg" /><figcaption><em>Diffie-Hellman key exchange mechanics preventing eavesdropping over untrusted networks.</em></figcaption></figure><p>In 1995, following a major password sniffing incident at the Helsinki University of Technology, Tatu Ylönen designed SSH-1 as a secure replacement. In 1999, the OpenBSD project developed OpenSSH, which became the global standard implementation, formalized as SSH-2 in RFCs 4251–4254 in 2006.</p><h3>Cryptographic Foundation: The Diffie-Hellman Key Agreement</h3><p>The central challenge of remote login across untrusted networks is key distribution: how can two machines agree on a shared secret without an eavesdropper deducing it?</p><p>Whitfield Diffie and Martin Hellman solved this in 1976 using modular arithmetic over a cyclic group. Here is a worked example using deliberately small numbers:</p><ol><li>Client and server publicly agree on a generator base g=5<em>g</em>=5 and a prime modulus p=23<em>p</em>=23.</li><li>The client picks a private secret a=6<em>a</em>=6 and computes the public value A=ga(modp)=56(mod23)=15625(mod23)=8<em>A</em>=<em>ga</em>(mod<em>p</em>)=56(mod23)=15625(mod23)=8. The client transmits A=8<em>A</em>=8.</li><li>The server picks a private secret b=15<em>b</em>=15 and computes the public value B=gb(modp)=515(mod23)=19<em>B</em>=<em>gb</em>(mod<em>p</em>)=515(mod23)=19. The server transmits B=19<em>B</em>=19.</li><li>The client computes the shared secret: K=Ba(modp)=196(mod23)=47045881(mod23)=2<em>K</em>=<em>Ba</em>(mod<em>p</em>)=196(mod23)=47045881(mod23)=2.</li><li>The server computes the shared secret: K=Ab(modp)=815(mod23)=35184372088832(mod23)=2<em>K</em>=<em>Ab</em>(mod<em>p</em>)=815(mod23)=35184372088832(mod23)=2.</li></ol><p>Both sides obtain the shared secret K=2<em>K</em>=2. An adversary observing the network sees g=5<em>g</em>=5, p=23<em>p</em>=23, A=8<em>A</em>=8, and B=19<em>B</em>=19. Computing a<em>a</em> or b<em>b</em> requires solving the discrete logarithm problem (gx≡A(modp)<em>gx</em>≡<em>A</em>(mod<em>p</em>)), which is computationally infeasible when p<em>p</em> is a 2048-bit prime or an elliptic curve (Curve25519).</p><h3>The Man-in-the-Middle (MITM) Threat</h3><p>Diffie-Hellman establishes encryption against passive wiretapping, but it is vulnerable to an active attacker who intercepts communications and negotiates independent keys with each side.</p><p>To prevent this, the SSH server signs the key exchange using its asymmetric host key (RSA, ECDSA, or Ed25519). The client verifies this signature against its local known_hosts file. On the very first connection, the client must rely on <strong>Trust On First Use (TOFU)</strong>, displaying the key fingerprint for manual user confirmation.</p><h3>Theory: Identity and the Reference Monitor</h3><p><em>Complete mediation, separation of identity, and the kernel boundary.</em></p><h4>Sources</h4><ul><li>Anderson, <em>Computer Security Technology Planning Study</em> (US Air Force, ESD-TR-73–51, 1972)</li><li>Saltzer &amp; Schroeder, <em>The Protection of Information in Computer Systems</em> (Proceedings of the IEEE, 1975)</li></ul><p>In 1972, James P. Anderson introduced the foundational concept of the <strong>Reference Monitor</strong>: an abstract machine that mediates all access attempts by active subjects to passive objects.</p><p><em>Figure 13: Anderson’s Reference Monitor concept: mediating process requests against object security policies.</em></p><p>Anderson defined three mandatory properties for any reference monitor:</p><ol><li><strong>Complete Mediation</strong>: The monitor must be invoked on every single access attempt without exception.</li><li><strong>Tamper Resistance</strong>: The monitor mechanism itself must be protected from unauthorized modification.</li><li><strong>Verifiability</strong>: The monitor must be small and simple enough to be formally analyzed and proven correct.</li></ol><p>In the Linux kernel, the Virtual Filesystem (VFS) and the Linux Security Module (LSM) hooks act as the reference monitor. When a process issues an open() or unlink() system call, the kernel intercepts execution, evaluates the credentials stored in the calling process&#39;s struct cred, and compares them with the target inode&#39;s mode and ownership fields.</p><h3>The AAA Framework</h3><p>Identity is partitioned across three distinct security operations:</p><ul><li><strong>Authentication</strong>: Verifying who the entity is (e.g., SSH password verification).</li><li><strong>Authorization</strong>: Determining what the authenticated entity is permitted to do (e.g., filesystem permission bit checks).</li><li><strong>Accounting (Auditing)</strong>: Recording what the entity actually did (e.g., Linux Audit logs via auditd).</li></ul><h3>Real vs. Effective UIDs</h3><p>Every Linux process maintains multiple UID attributes:</p><ul><li><strong>Real UID (RUID)</strong>: Identifies the actual user who spawned the process.</li><li><strong>Effective UID (EUID)</strong>: Evaluated by the kernel for filesystem and IPC permission checks.</li><li><strong>Saved UID (SUID)</strong>: Enables setuid binaries to drop and regain elevated privileges safely.</li></ul><p>When an unprivileged user executes a command, RUID and EUID match. In subsequent Ghost levels, understanding the divergence between RUID and EUID becomes the cornerstone of privilege escalation analysis.</p><h3>Theory: Enumeration as a Method (Kill Chain, ATT&amp;CK, NIST)</h3><p><em>Systematic discovery versus aimless exploration.</em></p><h4>Sources</h4><ul><li>Hutchins, Cloppert &amp; Amin, <em>Intelligence-Driven Computer Network Defense Informed by Analysis of Adversary Campaigns and Intrusion Kill Chains</em> (Lockheed Martin, 2011)</li><li>MITRE ATT&amp;CK Framework: <em>Discovery Tactic (TA0007)</em> (2015–Present)</li><li>NIST Special Publication 800–115, <em>Technical Guide to Information Security Testing and Assessment</em> (2008)</li></ul><p>Security assessments and forensic investigations do not proceed through random trial and error; they operate within structured threat models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wXIXtYZi-iB5M8CSD83mlg.jpeg" /><figcaption><em>Mapping local discovery commands to the Lockheed Martin Cyber Kill Chain and MITRE ATT&amp;CK.</em></figcaption></figure><h4>1. The Intrusion Kill Chain</h4><p>Published in 2011 by Lockheed Martin researchers, the Cyber Kill Chain defines the linear phases of a cyberattack: Reconnaissance → Weaponization → Delivery → Exploitation → Installation → Command &amp; Control (C2) → Actions on Objectives.</p><p>When an attacker establishes an interactive shell on a target, the engagement has passed the Exploitation phase. The attacker enters the initial phase of <strong>Actions on Objectives</strong>, where local internal discovery is required to understand the operating environment.</p><h4>2. MITRE ATT&amp;CK Mapping</h4><p>The MITRE ATT&amp;CK matrix decomposes adversary behavior into specific tactics and techniques. The actions in Level 0 map directly to the <strong>Discovery (TA0007)</strong> tactic:</p><ul><li><strong>T1033 (System Owner/User Discovery)</strong>: Running whoami or id to identify the active user context.</li><li><strong>T1083 (File and Directory Discovery)</strong>: Executing ls, find, or tree to map directory trees and locate sensitive files.</li><li><strong>T1552.001 (Credentials in Files)</strong>: Accessing unencrypted configuration files, archives, and flat files to harvest credentials.</li></ul><h4>3. NIST SP 800–115 Methodology</h4><p>NIST outlines four phases of penetration testing: Planning, Discovery, Attack, and Reporting. Discovery is the engine that drives exploitation: each command run on a target trades operational risk (generating audit logs) for information gain.</p><h3>Attacker vs. Defender Trade-offs: Quiet vs. Loud Enumeration</h3><ul><li><strong>Quiet Enumeration</strong>: Running native shell builtins and a minimal set of standard utilities (whoami, pwd, ls -la). This generates minimal telemetry and blends into normal administrative traffic.</li><li><strong>Loud Enumeration</strong>: Running automated post-exploitation scripts (such as LinPEAS or automated find loops) that touch thousands of files in seconds. This provides rapid discovery but triggers volume-based detection rules in modern Endpoint Detection and Response (EDR) sensors.</li></ul><h3>Theory: Access Control (The Matrix, DAC vs. MAC, and Design Principles)</h3><p><em>Lampson’s access matrix, the POSIX compression, and the Saltzer-Schroeder axioms.</em></p><h4>Sources</h4><ul><li>Lampson, <em>Protection</em> (ACM Operating Systems Review, 1974 / 5th Princeton Conference, 1971)</li><li>Saltzer &amp; Schroeder, <em>The Protection of Information in Computer Systems</em> (Proceedings of the IEEE, 1975)</li><li>Department of Defense Trusted Computer System Evaluation Criteria (TCSEC / “Orange Book”, DoD 5200.28-STD, 1983)</li></ul><p>In 1971, Butler Lampson formalized access control theory using the <strong>Access Matrix Model</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Djt9cl2bZfUKsiNGgS-OiA.jpeg" /><figcaption><em>Lampson’s Access Matrix model compressed into POSIX owner/group/other permission bits.</em></figcaption></figure><p>In Lampson’s model:</p><ul><li>The rows represent <strong>Subjects</strong> (active processes executing on behalf of users).</li><li>The columns represent <strong>Objects</strong> (passive entities: files, directories, sockets).</li><li>Each cell M[S,O]<em>M</em>[<em>S</em>,<em>O</em>] defines the exact access rights held by subject S<em>S</em> over object O<em>O</em>.</li></ul><p>Real-world operating systems cannot store an uncompressed matrix with thousands of users and millions of files. Systems store the matrix in one of two ways:</p><ol><li><strong>By Row (Capabilities)</strong>: Each process holds a ticket or capability list granting specific operations.</li><li><strong>By Column (Access Control Lists — ACLs)</strong>: Each object stores a list of authorized subjects and their permitted actions.</li></ol><h3>The Unix Compression</h3><p>Unix implements a compressed column-based ACL. Instead of listing every user individually, each file inode stores exactly 9 permission bits, divided into three triads:</p><ul><li><strong>Owner (User)</strong>: The identity owning the file.</li><li><strong>Group</strong>: The primary group associated with the file.</li><li><strong>Other</strong>: All other system users.</li></ul><p>While computationally efficient and requiring only 12 bits of storage per inode (including setuid, setgid, and sticky bits), this compression creates a structural flaw: an owner cannot grant read access to Alice and write access to Bob without creating a dedicated group.</p><h3>DAC vs. MAC</h3><ul><li><strong>Discretionary Access Control (DAC)</strong>: The resource owner determines access permissions. An unprivileged user can set mode 777 on their home directory or credentials file, exposing it to all users on the host.</li><li><strong>Mandatory Access Control (MAC)</strong>: Access decisions are governed by a central security policy enforced by the operating system kernel (e.g., SELinux, AppArmor), independent of user intent. Even if a file is mode 777, a MAC policy can block unauthorized process domains from reading it.</li></ul><h3>Saltzer &amp; Schroeder’s Design Principles (1975)</h3><p>Four of Jerome Saltzer and Michael Schroeder’s foundational security principles directly govern our analysis of Level 0:</p><ol><li><strong>Least Privilege</strong>: Every program and user should operate using the bare minimum set of privileges necessary. A credentials file should never exceed mode 600.</li><li><strong>Fail-Safe Defaults</strong>: Access decisions must default to denial. Default umask configurations in standard Linux distributions (e.g., 022) create files that are world-readable (644) by default, violating this principle.</li><li><strong>Complete Mediation</strong>: Every access to every object must be checked on every access attempt, preventing cached permission bypasses.</li><li><strong>Psychological Acceptability</strong>: Security mechanisms must not impose excessive cognitive or operational friction. When secure secret management is difficult, administrators store plain passwords in local text files.</li></ol><h3>Theory: A History of Getting Secrets Wrong</h3><p><em>From early plaintext shadow files to modern honeytokens and canary credentials.</em></p><h4>Sources</h4><ul><li>Morris &amp; Thompson, <em>Password Security: A Case History</em> (Communications of the ACM, 1979)</li><li>MITRE Common Weakness Enumeration: <em>CWE-798: Use of Hard-coded Credentials</em> &amp; <em>CWE-312: Cleartext Storage of Sensitive Information</em></li><li>Paes de Barros, <em>The Honeytoken Approach</em> (SecurityFocus, 2003)</li><li>CyberArk Threat Research, <em>Unpacking the Uber Breach</em> (2022)</li></ul><p>The file credentials discovered in Level 0 represents a recurring design failure spanning five decades of computing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_SoX7QQDZ_QQwCrNe48NZA.jpeg" /><figcaption><em>The evolution of password protection: from world-readable /etc/passwd to modern shadow storage and canary tokens.</em></figcaption></figure><p>In early Unix (First through Sixth Editions), /etc/passwd was world-readable (644) because programs like ls needed it to map numeric UIDs to username strings. The file contained the hashed password representations.</p><p>In 1979, Robert Morris and Ken Thompson published <em>Password Security: A Case History</em>. They demonstrated that an attacker using a PDP-11/70 could compute approximately 800 password hashes per second. Using a dictionary of 250,000 words, an entire system’s password file could be cracked in five minutes.</p><p>Their initial defense was the introduction of a <strong>salt</strong> — a pseudo-random string appended to the password before hashing, preventing global rainbow table attacks. However, as computing hardware accelerated, leaving hashes accessible to unprivileged users became untenable. In 1988, the Morris Worm used automated dictionary attacks against /etc/passwd to propagate across the internet.</p><p>The structural remedy was the <strong>Shadow Suite</strong>: moving the password hashes out of /etc/passwd into /etc/shadow, a file restricted to mode 640 or 600 owned by root:</p><pre>$ cat /etc/shadow<br>cat: /etc/shadow: Permission denied</pre><p>This permission denial is complete mediation working as intended.</p><h3>The Modern Threat: CWE-798 and Hard-Coded Credentials</h3><p>While system password hashes were moved to shadow files, application credentials frequently remain in flat files:</p><ul><li><strong>CWE-798</strong>: Use of Hard-coded Credentials.</li><li><strong>CWE-312</strong>: Cleartext Storage of Sensitive Information.</li><li><strong>CWE-256</strong>: Plaintext Storage of a Password.</li></ul><p>In 2022, according to published threat research by CyberArk and public disclosures, an adversary compromised an internal network share at Uber. In a PowerShell script stored in a readable folder, the intruder located hard-coded credentials for an administrative service account. Those static credentials provided access to the enterprise’s privileged access management vault. No vulnerability exploit was required; the adversary simply read a file that was exposed through lax discretionary access controls.</p><h3>Turning the Tables: The Honeytoken Strategy</h3><p>In 2003, Brazilian researcher Augusto Paes de Barros introduced the concept of the <strong>honeytoken</strong>: an attractive piece of data (a credential, API key, or database record) that has no legitimate operational purpose.</p><p>Defenders deploy canary credentials into filesystem paths where intruders typically hunt (~/.aws/credentials, archive/credentials, .env). When an adversary discovers and attempts to authenticate using the token, the central authentication system triggers an immediate, high-fidelity security alert. Through honeytokens, the attacker&#39;s enumeration loop is transformed into a detection tripwire.</p><h3>Theory: The Theory Behind the Loop</h3><p><em>Synthesizing the practical workflow with fundamental computer science principles.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MTMz-4qG4WZLFfeVpNLzEA.jpeg" /><figcaption><em>The complete orientation loop: mapping practical execution back to computer science models.</em></figcaption></figure><p>Every command executed in the first sixty seconds of an engagement directly probes a core operating system structure:</p><ol><li><strong>Who am I? (</strong><strong>whoami)</strong> →→ Queries the process subject credentials evaluated by the reference monitor.</li><li><strong>Where am I? (</strong><strong>pwd)</strong> →→ Establishes the current namespace node in the rooted directory hierarchy.</li><li><strong>What is here? (</strong><strong>ls -la)</strong> →→ Probes the directory table and maps visible and hidden inode entries.</li><li><strong>What can I do? (</strong><strong>stat)</strong> →→ Evaluates the compressed access matrix governing subject-object interactions.</li><li><strong>What is the content? (</strong><strong>file / </strong><strong>cat -A)</strong> →→ Treats files as raw byte streams while guarding against terminal control injection.</li></ol><h3>The Defensive Mirror</h3><p>For every offensive discovery action, there is a corresponding defensive control:</p><ul><li>Adversary queries process identity →→ Defender monitors suspicious account enumeration via auditd or EDR.</li><li>Adversary enumerates directory entries →→ Defender enforces strict DAC modes and restricts directory traverse bits.</li><li>Adversary hunts for credentials →→ Defender implements automated secrets management (HashiCorp Vault, AWS Secrets Manager) and deploys honeytoken tripwires.</li></ul><h3>The Quiet Trap</h3><p>A quiet trap is an operational failure mode that executes without generating an error message, leading the analyst to incorrect conclusions.</p><pre>$ cat notes.txt | grep &#39;rotate&#39;<br>todo: rotate keys</pre><p>The command appears to succeed. However, because notes.txt originated from a Windows environment, it contains carriage returns (\r). If we extract the token into an environment variable:</p><pre>$ TOKEN=$(cat notes.txt | head -n 1 | awk &#39;{print $2}&#39;)<br>$ curl -H &quot;Authorization: Bearer $TOKEN&quot; https://api.internal/auth</pre><p>The HTTP request fails or corrupts the header because the carriage return is treated as a header termination delimiter.</p><h3>The Rule of Thumb</h3><p>Whenever inspecting text files on an unknown system, always verify line endings using file or cat -A. If CRLF terminators are detected, sanitize the file using dos2unix or tr -d &#39;\r&#39; before passing values to downstream tools.</p><h3>Why These Concepts Matter</h3><ul><li><strong>Rooted Simplicity</strong>: Understanding that directories are simple tables mapping names to inodes demystifies filesystem navigation.</li><li><strong>Security Boundaries</strong>: Recognizing that the kernel respects numeric UIDs rather than shell variables prevents reliance on spoofable user-space data.</li><li><strong>Discretionary Vulnerabilities</strong>: Discretionary access control places the burden of protection on individual users, making configuration drift and credential exposure inevitable in large environments.</li><li><strong>Detection Engineering</strong>: Understanding that enumeration produces a distinct telemetry trail enables defenders to build high-fidelity behavioral detection rules.</li></ul><h3>Key Takeaways</h3><ol><li><strong>Orientation First</strong>: Never run automated scripts before manually confirming identity (whoami) and location (pwd).</li><li><strong>Always Use </strong><strong>ls -la</strong>: Default directory listings conceal dot-files where sensitive configuration and credentials reside.</li><li><strong>Check Directory Execute Bits</strong>: Reading a directory requires r, but accessing files inside it requires x.</li><li><strong>Identify Before Reading</strong>: Use file and cat -A to avoid terminal corruption and detect hidden carriage return traps.</li><li><strong>Credentials in Files Are Real</strong>: MITRE ATT&amp;CK technique T1552.001 remains a primary initial compromise vector in real-world breaches.</li></ol><h3>The BreachLab Journey Continues</h3><p>Level 0 establishes the orientation baseline. In subsequent levels of the Ghost track, the challenges become progressively more constrained:</p><ul><li>Level 1 introduces unusual filename syntax (spaces, special characters, leading hyphens) that breaks naive shell command lines.</li><li>Subsequent levels introduce restricted shells, custom binaries, network listener inspection, and cron automation.</li></ul><p>The orientation loop mastered here — identifying identity, location, and readable surface — remains identical across every level of the wargame and every enterprise network engagement.</p><h3>Follow along with the complete playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLUep6spDxjvo%26si%3DuwtryYSM125deaiu&amp;type=text%2Fhtml&amp;schema=youtube&amp;display_name=YouTube&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLUep6spDxjvo" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9346f4e1dfbe95740f629d65a4db3b24/href">https://medium.com/media/9346f4e1dfbe95740f629d65a4db3b24/href</a></iframe><h3>Platform</h3><ul><li><strong>BreachLab</strong>: <a href="https://breachlab.org">https://breachlab.org</a></li><li><strong>Ghost Track Level 0</strong>: <a href="https://breachlab.org/tracks/ghost/i/0">https://breachlab.org/tracks/ghost/i/0</a></li><li><strong>Platform Rules</strong>: <a href="https://breachlab.org/rules">https://breachlab.org/rules</a></li><li><strong>Support BreachLab</strong>: <a href="https://liberapay.com/breachlab/donate">https://liberapay.com/breachlab/donate</a></li></ul><p><em>BreachLab is an independent platform dedicated to hands-on cybersecurity education. If content built on its tracks earns revenue, consider supporting the platform via Liberapay to keep it free and accessible for everyone.</em></p><h3>GitHub Repository</h3><p>Follow along with the source code and production pipeline tools in the official repository:</p><p><a href="https://github.com/Someone-anon-coder">Someone-anon-coder - Overview</a></p><h3>Follow the Complete Series</h3><p>Access every written guide and theoretical deep dive in the official Medium publication series:</p><p><a href="https://computer-info-1.medium.com/list/breachlab-ghost-i-24dad052016f">List: BreachLab · Ghost I | Curated by Someone | Medium</a></p><h3>Explore My Other Learning Series</h3><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">Arbiter Guide</a> — Advanced Java Systems Architecture</li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">Arbiter Guide Theory</a> — Theoretical Foundations of Distributed Java</li><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">Cybersecurity</a> — Offensive Security, Wargames, and Threat Analysis</li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">Python Guide</a> — Python Systems &amp; Automation</li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">Python Guide Theory</a> — Python Internals, CPython VM, and Runtime Design</li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">GoLang Guide</a> — High-Performance Concurrency in Go</li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">GoLang Guide Theory</a> — Go Runtime, Memory Model, and Scheduler Internals</li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">Matplotlib</a> — Visual Analytics &amp; Data Presentation</li><li><a href="https://computer-info-1.medium.com/list/breachlab-ghost-i-24dad052016f">BreachLab · Ghost I</a> — Level-by-Level Practical &amp; Theoretical Walkthroughs</li></ul><h3>Sources</h3><ol><li>Ritchie, D. M., &amp; Thompson, K. (1974). <em>The UNIX Time-Sharing System</em>. Communications of the ACM, 17(7), 365–375. <a href="https://www.nokia.com/bell-labs/about/dennis-m-ritchie/manintro.html">https://www.nokia.com/bell-labs/about/dennis-m-ritchie/manintro.html</a></li><li>Filesystem Hierarchy Standard Group. (2015). <em>Filesystem Hierarchy Standard (FHS 3.0)</em>. Linux Foundation. <a href="https://refspecs.linuxfoundation.org/fhs">https://refspecs.linuxfoundation.org/fhs</a></li><li>Ylönen, T., &amp; Lonvick, C. (2006). <em>The Secure Shell (SSH) Protocol Architecture</em> (RFC 4251). Internet Engineering Task Force. <a href="https://www.rfc-editor.org/rfc/rfc4251">https://www.rfc-editor.org/rfc/rfc4251</a></li><li>Saltzer, J. H., &amp; Schroeder, M. D. (1975). <em>The Protection of Information in Computer Systems</em>. Proceedings of the IEEE, 63(9), 1278–1308. <a href="https://www.cs.virginia.edu/~evans/cs551/saltzer/">https://www.cs.virginia.edu/~evans/cs551/saltzer/</a></li><li>Lampson, B. W. (1974). <em>Protection</em>. ACM Operating Systems Review, 8(1), 18–24. <a href="https://bwlampson.site/08-Protection/WebPage.html">https://bwlampson.site/08-Protection/WebPage.html</a></li><li>Morris, R., &amp; Thompson, K. (1979). <em>Password Security: A Case History</em>. Communications of the ACM, 22(11), 594–597. <a href="https://www.passwordresearch.com/papers/paper41.html">https://www.passwordresearch.com/papers/paper41.html</a></li><li>Scarfone, K., Souppaya, M., Cody, A., &amp; Orebaugh, A. (2008). <em>Technical Guide to Information Security Testing and Assessment</em> (NIST SP 800–115). National Institute of Standards and Technology. <a href="https://csrc.nist.gov/pubs/sp/800/115/final">https://csrc.nist.gov/pubs/sp/800/115/final</a></li><li>Hutchins, E. M., Cloppert, M. J., &amp; Amin, R. M. (2011). <em>Intelligence-Driven Computer Network Defense Informed by Analysis of Adversary Campaigns and Intrusion Kill Chains</em>. Lockheed Martin Corporation. <a href="https://www.lockheedmartin.com/en-us/capabilities/cyber/cyber-kill-chain.html">https://www.lockheedmartin.com/en-us/capabilities/cyber/cyber-kill-chain.html</a></li><li>MITRE Corporation. (2015). <em>Discovery (TA0007) — Enterprise Matrix</em>. MITRE ATT&amp;CK. <a href="https://attack.mitre.org/tactics/TA0007/">https://attack.mitre.org/tactics/TA0007/</a></li><li>MITRE Corporation. (2006). <em>CWE-798: Use of Hard-coded Credentials</em>. Common Weakness Enumeration. <a href="https://cwe.mitre.org/data/definitions/798.html">https://cwe.mitre.org/data/definitions/798.html</a></li><li>CyberArk Labs. (2022). <em>Unpacking the Uber Breach: Technical Threat Analysis</em>. CyberArk Software. <a href="https://www.cyberark.com/resources/threat-research/unpacking-the-uber-breach">https://www.cyberark.com/resources/threat-research/unpacking-the-uber-breach</a></li></ol><p><em>This article was prepared with AI assistance. The companion video’s narration is synthesized speech and the presenter is an animated illustration, not a real person. This guide teaches methodology and intentionally does not reveal wargame solutions or passwords.</em></p><h3>What’s Next?</h3><p>In the next installment, we tackle <strong>Level 1: Name Game</strong>. We confront filenames containing whitespace, special escape sequences, and leading dashes, exploring how the shell’s argument parsing treats options and filenames, and how operators manipulate files that seem impossible to open.</p><p>Happy hacking!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba118b6ef3e0" width="1" height="1" alt=""><hr><p><a href="https://infosecwriteups.com/linux-enumeration-and-access-control-what-happens-in-the-first-sixty-seconds-on-a-target-ba118b6ef3e0">Linux Enumeration and Access Control: What Happens in the First Sixty Seconds on a Target</a> was originally published in <a href="https://infosecwriteups.com">InfoSec Write-ups</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Java Streams — The Theory Behind Lazy Evaluation, Folds, and the Optional Type]]></title>
            <link>https://medium.com/codex/understanding-java-streams-the-theory-behind-lazy-evaluation-folds-and-the-optional-type-a680e8492df3?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/a680e8492df3</guid>
            <category><![CDATA[day-14]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[theory]]></category>
            <category><![CDATA[stream]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 13:20:43 GMT</pubDate>
            <atom:updated>2026-11-04T15:50:25.643Z</atom:updated>
            <content:encoded><![CDATA[<h4>Arbiter Guide Theory • Day 14 | Where map and reduce came from, why a stream waits for a terminal operation, what an identity value really promises, and why orElse and orElseGet differ in evaluation, not in type</h4><figure><img alt="A blue wireframe pipeline with a closed terminal valve, a fold arrow collapsing numbers into one value, a grouping frame with summed trays, and an empty Optional capsule beside an unopened thunk envelope" src="https://cdn-images-1.medium.com/max/1024/1*Qgg3X44hj4Bepn8TzPy41g.png" /><figcaption><em>A stream is a lazy list from 1965. reduce is a fold from 1962. Optional is a type from the languages that never had null.</em></figcaption></figure><p>Here’s the source code for today’s lesson:</p><pre>import java.util.List;<br>import java.util.Map;<br>import java.util.Optional;<br>import java.util.stream.Collectors;<br><br>public class java_14StreamsOptional {<br>    public static void main(String[] args) {<br>        List&lt;String&gt; words = List.of(&quot;apple&quot;, &quot;kiwi&quot;, &quot;banana&quot;, &quot;fig&quot;, &quot;cherry&quot;, &quot;date&quot;);<br>        <br>        String str = words.stream()<br>            .filter(word -&gt; word.length() &gt; 3)<br>            .map(String::toUpperCase)<br>            .collect(Collectors.joining(&quot;, &quot;));<br>        <br>        int total = words.stream()<br>            .mapToInt(String::length)<br>            .reduce(0, Integer::sum);<br>        <br>        System.out.println(str);<br>        System.out.println(&quot;Total characters: &quot; + total);<br>        System.out.println();<br><br>        List&lt;Transaction&gt; transactions = List.of(<br>            new Transaction(&quot;Alice&quot;, &quot;Groceries&quot;, 45.20),<br>            new Transaction(&quot;Bob&quot;, &quot;Electronics&quot;, 299.99),<br>            new Transaction(&quot;Alice&quot;, &quot;Electronics&quot;, 150.00),<br>            new Transaction(&quot;Carol&quot;, &quot;Groceries&quot;, 60.00),<br>            new Transaction(&quot;Alice&quot;, &quot;Groceries&quot;, 12.50),<br>            new Transaction(&quot;Bob&quot;, &quot;Groceries&quot;, 22.30)<br>        );<br>        <br>        Map&lt;String, List&lt;Transaction&gt;&gt; customerTransactions = transactions.stream()<br>            .collect(Collectors.groupingBy(Transaction::customer));<br><br>        System.out.println(&quot;--- Customer Totals ---&quot;);<br>        System.out.printf(&quot;Alice = %.2f%n&quot;, totalFor(customerTransactions, &quot;Alice&quot;));<br>        System.out.printf(&quot;Bob = %.2f%n&quot;, totalFor(customerTransactions, &quot;Bob&quot;));<br>        System.out.printf(&quot;Dave = %.2f%n&quot;, totalFor(customerTransactions, &quot;Dave&quot;));<br><br>        Map&lt;String, Double&gt; categoryTotals = transactions.stream()<br>            .collect(Collectors.groupingBy(<br>                Transaction::category,<br>                Collectors.summingDouble(Transaction::amount)<br>            ));<br><br>        System.out.println(&quot;\n--- Category Totals ---&quot;);<br>        categoryTotals.forEach((category, totalCount) -&gt; <br>            System.out.printf(&quot;%s = %.2f%n&quot;, category, totalCount)<br>        );<br>    }<br>    <br>    record Transaction(String customer, String category, double amount) {}<br><br>    public static double totalFor(Map&lt;String, List&lt;Transaction&gt;&gt; byCustomer, String customer) {<br>        return Optional.ofNullable(byCustomer.get(customer))<br>            .map(list -&gt; list.stream()<br>            .mapToDouble(Transaction::amount)<br>            .sum()<br>        ).orElseGet(() -&gt; {<br>            System.out.println(&quot;No transactions found for &quot; + customer);<br>            return 0.0;<br>        });<br>    }<br>}</pre><p>The <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1"><strong><em>Day 14 Guide</em></strong></a> walks through what this program does: a filter → map → collect pipeline, a reduce that sums word lengths, two groupingBy calls over a list of transaction records, and an Optional chain that handles a customer who isn&#39;t in the data. This article is about <em>why</em> it behaves the way it does.</p><p>As with Day 13, almost none of it started in Java. Mapping a function over a list was in the first LISP paper. Reducing a list with an operator was a single symbol in a 1962 notation. The idea that a sequence can be computed only as it’s consumed was named in 1965 and argued for as a design principle in 1989. Grouping and totaling in one step is SQL’s GROUP BY. And the case for Optional was made, most memorably, by the man who introduced null in the first place. What Java 8 added is a library that brings all of these together, plus a few rules, like &quot;identity&quot; and &quot;use a stream only once,&quot; that only make sense once you know where they came from.</p><p>If you missed the previous lesson, the <a href="https://medium.com/codex/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9">Day 13 Guide Theory</a> covers the theory behind lambdas, and the <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9">Day 13 Guide</a> covers the code.</p><h3>map, filter, and reduce: An Old Vocabulary</h3><p><em>Operations on whole lists, not on indexes</em></p><h4>From LISP’s maplist and APL’s reduction to Bird’s theory of lists</h4><p>The lesson’s first pipeline never mentions an index:</p><pre>String str = words.stream()<br>    .filter(word -&gt; word.length() &gt; 3)<br>    .map(String::toUpperCase)<br>    .collect(Collectors.joining(&quot;, &quot;));</pre><p>The idea of applying a function to every element of a list, as one operation, goes back to John McCarthy’s 1960 LISP paper, “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I” (<em>Communications of the ACM</em>, 3(4)). Alongside LAMBDA, which Day 13 traced to Church, the paper defined maplist, a function that takes another function and applies it across a list. That is map, about fifty years before java.util.stream.</p><p>The second pipeline’s operation has an equally old ancestor:</p><pre>int total = words.stream()<br>    .mapToInt(String::length)<br>    .reduce(0, Integer::sum);</pre><p>Kenneth Iverson’s <em>A Programming Language</em> (Wiley, 1962), the book that became APL, gave this its own operator: <strong>reduction</strong>, written /. In APL, +/ applied to a list of numbers adds them all together. That&#39;s exactly what reduce(..., Integer::sum) does: it places the + between every pair of elements.</p><p>These operations were later given a mathematical theory. Richard Bird’s “An Introduction to the Theory of Lists” (in <em>Logic of Programming and Calculi of Discrete Design</em>, Springer, 1987) treated map, filter, and reduction as algebraic objects with laws, so programs built from them could be reasoned about like equations. Graham Hutton&#39;s &quot;A Tutorial on the Universality and Expressiveness of Fold&quot; (<em>Journal of Functional Programming</em>, 9(4), 1999) went further and showed that a single operation, <strong>fold</strong>, is enough to express a wide range of list functions, including map and filter themselves. Java&#39;s reduce is a fold.</p><h3>What reduce Assumes: Identity and Associativity</h3><p><em>The rules in the Javadoc are algebra, not style</em></p><h4>Why the starting value must change nothing, and the run that proves it</h4><p>reduce(0, Integer::sum) takes two arguments, and the Stream.reduce documentation places conditions on both. The first, 0, must be an <strong>identity</strong> for the accumulator: combining it with any value x must give back x. The second, Integer::sum, must be <strong>associative</strong>: (a + b) + c must equal a + (b + c).</p><p>In algebra, a set with an associative operation and an identity element is called a <strong>monoid</strong>. Integers under addition with 0 are the textbook example. Bird&#39;s 1987 theory relied on exactly these two properties, and they&#39;re why reduce asks for them. With associativity, a list can be split into pieces, each piece reduced separately, and the results combined, without changing the answer. That is precisely what a parallel stream does.</p><p>The identity rule looks pedantic until it’s broken. Running sequentially, an identity of 10 just looks like &quot;start counting from 10&quot;:</p><pre>seq identity 0  = 28<br>par identity 0  = 28<br>seq identity 10 = 38<br>par identity 10 = 88</pre><p>With 0, sequential and parallel agree. With 10, they don&#39;t. In parallel, the stream split the six words into pieces, and each piece started its own sum from the identity. Here, each of the six elements ended up with its own 10: 28 + 6 × 10 = 88. The exact number depends on how the stream happens to split, which is the problem: a value that isn&#39;t really an identity makes the result depend on scheduling. The lesson uses 0, a true identity for +, so its reduce is correct in both modes, even though it only ever runs sequentially.</p><h3>Laziness: Why Nothing Runs Until a Terminal Operation Asks</h3><p><em>A stream is a promise to compute, not a computation</em></p><h4>Landin’s streams, lazy evaluators, and Hughes on why it matters</h4><p>The Guide ran the first pipeline without a terminal operation, with prints inside each lambda, and nothing printed at all. With collect added, the trace looked like this:</p><pre>filter apple<br>map apple<br>filter kiwi<br>map kiwi<br>...<br>filter fig<br>filter cherry<br>map cherry</pre><p>Both behaviors come from a single design decision. The java.util.stream package documentation states it directly: streams are lazy, and computation on the source only happens once the terminal operation starts.</p><p>The word <em>stream</em> itself comes from Peter Landin. In “A Correspondence Between ALGOL 60 and Church’s Lambda-Notation” (<em>Communications of the ACM</em>, 8(2), 1965), Landin described streams as lists whose later elements are not computed until they’re needed. In 1976, two papers made that idea a general evaluation strategy: Daniel Friedman and David Wise’s “CONS Should Not Evaluate Its Arguments” (ICALP 1976) and Peter Henderson and James Morris’s “A Lazy Evaluator” (POPL 1976). Both argued that building a data structure should not force its contents to be computed. Abelson and Sussman later made streams a central chapter of <em>Structure and Interpretation of Computer Programs</em> (MIT Press, 1985, §3.5), where they’re built from exactly this kind of delayed list.</p><p>The trace shows the payoff. Elements move through the whole pipeline <strong>one at a time</strong>: apple is filtered <em>and</em> mapped before kiwi is looked at. No intermediate &quot;filtered list&quot; is ever built. Philip Wadler named the compiler version of this optimization <strong>deforestation</strong>, in &quot;Deforestation: Transforming Programs to Eliminate Trees&quot; (<em>Theoretical Computer Science</em>, 73(2), 1990): when one list operation feeds another, the intermediate structure can be removed entirely. Java&#39;s streams get the same effect by design rather than by compiler transformation, because each element is pulled through the chain on demand.</p><p>John Hughes’s “Why Functional Programming Matters” (<em>The Computer Journal</em>, 32(2), 1989) explained why laziness is worth having beyond saving work. He called it a kind of <em>glue</em>: it lets you write the producer of data and the consumer of data as separate pieces, and the consumer decides how much is actually computed. In the lesson, filter and map describe <em>what</em> should happen, and collect decides <em>that</em> it happens. fig never reaching map is the smallest possible demonstration.</p><h3>Why a stream can only be used once</h3><p>The Guide also showed that a second terminal operation on the same stream throws IllegalStateException. That follows from the same model. A Java stream is not a lazy <em>list</em> in Landin&#39;s sense, where computed elements are kept and can be revisited. It&#39;s closer to an iterator: a one-way cursor over its source that&#39;s consumed as it&#39;s pulled. Once the terminal operation has pulled every element through, there&#39;s nothing left to pull. The package documentation states the rule plainly: a stream should be operated on only once. The source, words, is untouched, which is why the lesson can call words.stream() again for the next pipeline.</p><h3>Records: Data as a Named Tuple</h3><p><em>A class that is only its data</em></p><h4>JEP 395 and the product types of typed functional languages</h4><p>The transactions in the lesson are a single line:</p><pre>record Transaction(String customer, String category, double amount) {}</pre><p>Running javap -p on the compiled class shows what that line produced:</p><pre>final class java_14StreamsOptional$Transaction extends java.lang.Record {<br>  private final java.lang.String customer;<br>  private final java.lang.String category;<br>  private final double amount;<br>  java_14StreamsOptional$Transaction(java.lang.String, java.lang.String, double);<br>  public final java.lang.String toString();<br>  public final int hashCode();<br>  public final boolean equals(java.lang.Object);<br>  public java.lang.String customer();<br>  public java.lang.String category();<br>  public double amount();<br>}</pre><p>The class is final, every field is private final, and equals, hashCode, and toString are generated from the components, which is everything Day 5 wrote by hand. Records were finalized in Java 16 by <strong>JEP 395</strong>, after previews in Java 14 and 15. Brian Goetz&#39;s design note &quot;Data Classes and Sealed Types for Java&quot; (2019) framed them as <strong>nominal tuples</strong>: a fixed group of values with names, whose identity is entirely its data.</p><p>In type theory, a group of values held together is a <strong>product type</strong>, and typed functional languages had them as a core feature long before Java. The language Hope, described by Rod Burstall, David MacQueen, and Don Sannella in “HOPE: An Experimental Applicative Language” (LISP Conference, 1980), is one of the early languages where user-defined data types of this kind were central. The accessor names, customer() rather than getCustomer(), reflect that view: a component is part of what the record <em>is</em>, not a property hidden behind a getter. That&#39;s also what makes Transaction::customer read naturally as the classifier for groupingBy.</p><h3>groupingBy: SQL’s GROUP BY in a Collector</h3><p><em>Partition first, then aggregate each part</em></p><h4>Codd’s relations, SEQUEL’s aggregates, and mutable reduction</h4><p>The lesson’s second grouping does two things at once:</p><pre>Map&lt;String, Double&gt; categoryTotals = transactions.stream()<br>    .collect(Collectors.groupingBy(<br>        Transaction::category,<br>        Collectors.summingDouble(Transaction::amount)<br>    ));</pre><p>Anyone who has written SQL has seen this shape before:</p><pre>SELECT category, SUM(amount) FROM transactions GROUP BY category;</pre><p>Edgar Codd’s “A Relational Model of Data for Large Shared Data Banks” (<em>Communications of the ACM</em>, 13(6), 1970) set out the idea of data as relations, sets of tuples, which a List&lt;Transaction&gt; of records closely resembles. Donald Chamberlin and Raymond Boyce&#39;s &quot;SEQUEL: A Structured English Query Language&quot; (ACM SIGFIDET Workshop, 1974), the language that became SQL, brought grouping and aggregate functions to that model: split the rows into groups by a column, then compute one value per group. groupingBy follows the same two steps. The <strong>classifier</strong>, Transaction::category, is the GROUP BY column. The <strong>downstream collector</strong>, summingDouble(Transaction::amount), is the SUM(amount).</p><p>Under the hood, a Collector is a recipe for what the java.util.stream documentation calls <strong>mutable reduction</strong>. Where reduce builds a new value at each step, a collector supplies a mutable container and folds each element <em>into</em> it. The Collector interface defines this as four functions: a <strong>supplier</strong> that creates the container, an <strong>accumulator</strong> that adds one element to it, a <strong>combiner</strong> that merges two containers (for parallel streams), and an optional <strong>finisher</strong>. groupingBy is a collector whose container is a map, and whose accumulator hands each element to the downstream collector for its group. That&#39;s why it can total while it groups, in a single pass, with no list of transactions ever built per category.</p><p>One rule from the documentation matters for the output: groupingBy makes no guarantees about the type, mutability, or ordering of the map it returns. The Guide checked, and it&#39;s a HashMap. In relational terms that&#39;s no surprise either: Codd&#39;s relations are <em>sets</em>, and a GROUP BY without an ORDER BY has no defined row order.</p><h3>Optional: The Type That Replaces null</h3><p><em>Absence as a value, not a missing pointer</em></p><h4>Hoare’s “billion-dollar mistake,” the Maybe type, and Moggi’s monads</h4><p>totalFor begins with a call that might return null:</p><pre>return Optional.ofNullable(byCustomer.get(customer))</pre><p>Tony Hoare introduced the null reference in ALGOL W in 1965, and in a 2009 talk at QCon London titled “Null References: The Billion Dollar Mistake,” he described it as his own error: a value that has a type, but can’t do anything that type promises. Every NullPointerException is that mismatch showing up at runtime.</p><p>Typed functional languages took the opposite approach. Instead of letting every reference possibly be null, they used a separate type for “maybe there’s a value”: option in ML, and Maybe in Haskell. A function that might find nothing says so in its return type, and the caller has to handle both cases to get the value out. Optional&lt;T&gt; is Java&#39;s version of that idea. The Optional Javadoc describes its intended use narrowly: primarily as a method <strong>return type</strong>, where &quot;no result&quot; is a real possibility and a null would be likely to cause errors. totalFor&#39;s lookup is exactly that case.</p><p>The way the lesson <em>uses</em> Optional has its own theory. .map(...) applies a function to the value inside, if there is one, and returns another Optional. In category theory this is a <strong>functor</strong>, and the pattern of chaining such steps was given its programming-language foundation by Eugenio Moggi&#39;s &quot;Notions of Computation and Monads&quot; (<em>Information and Computation</em>, 93(1), 1991), then made practical by Philip Wadler&#39;s &quot;Monads for Functional Programming&quot; (in <em>Advanced Functional Programming</em>, LNCS 925, 1995). The point of both papers is the same: an effect like &quot;might be missing&quot; can be carried by the type, so the code in the middle of the chain never has to check for it. That&#39;s why totalFor contains no if and no isPresent().</p><p>Java’s Optional isn&#39;t a perfectly faithful version of the theory. Its map has one special rule: if the function returns null, the result is an empty Optional, not an Optional holding null. I checked:</p><pre>Optional.of(&quot;x&quot;).map(s -&gt; null)   // Optional.empty</pre><p>That’s a practical choice for a language that still has null everywhere, but it means map doesn&#39;t always obey the functor law that mapping f and then g equals mapping their composition. In the lesson, the mapped function returns a double, which can never be null, so this edge case never arises.</p><h3>Two Methods, One Type: Call-by-Value vs. Call-by-Need</h3><p><em>The quiet trap, viewed from the theory side</em></p><h4>ALGOL 60’s call-by-name, Ingerman’s thunks, and Plotkin’s evaluation strategies</h4><p>The Guide swapped orElseGet for orElse and got this:</p><pre>No transactions found for Alice<br>Alice = 207.70<br>No transactions found for Bob<br>Bob = 322.29<br>No transactions found for Dave<br>Dave = 0.00</pre><p>The totals are correct and the log is wrong. The cause is an idea older than Java by four decades: <strong>evaluation strategy</strong>, the rule for when a function’s arguments are computed.</p><p>Java uses <strong>call-by-value</strong>. Before orElse(fallback(c)) runs, Java evaluates fallback(c), prints its message, and passes in the resulting 0.0. Whether orElse ends up using it is irrelevant. The work is already done.</p><p>The ALGOL 60 report (Peter Naur, ed., <em>Communications of the ACM</em>, 3(5), 1960) defined a different option, <strong>call-by-name</strong>, where an argument is not evaluated at the call. It’s re-evaluated each time the function actually uses it. Implementing that required packaging an unevaluated expression into something that could be run later. P. Z. Ingerman named that package in “Thunks: A Way of Compiling Procedure Statements with Some Comments on Procedure Declarations” (<em>Communications of the ACM</em>, 4(1), 1961): a <strong>thunk</strong>, a small piece of code that computes an argument on demand. Gordon Plotkin’s “Call-by-Name, Call-by-Value and the λ-Calculus” (<em>Theoretical Computer Science</em>, 1(2), 1975) placed both strategies on formal footing, in the same lambda calculus Day 13 started from. The lazy evaluators of 1976 added <strong>call-by-need</strong>: evaluate on first use, at most once.</p><p>orElseGet is call-by-need, written by hand. Its parameter is a Supplier&lt;T&gt;, a lambda with no arguments that produces a value when its get() method is called. That&#39;s exactly a thunk. orElseGet calls it only if the Optional is empty, and at most once. orElse takes a plain T, so the caller&#39;s argument has already been evaluated by the time orElse sees it. It&#39;s the same distinction as the stream pipeline earlier: a description of work versus the work itself.</p><h3>The theory gap: the type system can’t see the difference</h3><p>Here are the two signatures:</p><pre>T orElse(T other)<br>T orElseGet(Supplier&lt;? extends T&gt; supplier)</pre><p>Both return a T. Both produce the same value in every case. In Java&#39;s type system, nothing distinguishes &quot;computes the fallback only when needed&quot; from &quot;computes it every time and throws it away,&quot; because Java&#39;s types describe <em>what</em> a computation returns, not <em>what it does</em> along the way. Printing a line, querying a database, or allocating an object are all invisible to the compiler.</p><p>That’s a known limit, and type theory has an answer to it. James Lucassen and David Gifford’s “Polymorphic Effect Systems” (POPL 1988) proposed extending types with <strong>effects</strong>, so a function’s signature records whether it reads, writes, or performs other side effects. In a language with an effect system, fallback(c) would carry its printing effect in its type, and the cost of evaluating it eagerly would be visible at the call site. Java has no such system. Neither did the Day 13 trap, where a handler that discarded an exception&#39;s message had the same type as one that logged it. In both cases, the type checker accepts every version, and the difference has to come from the programmer&#39;s understanding of when code runs.</p><h3>Why These Concepts Matter</h3><ul><li><strong>map and reduction</strong> predate Java by decades: McCarthy&#39;s maplist (1960) and Iverson&#39;s / operator (1962), later formalized by Bird (1987) and shown by Hutton (1999) to be expressible as <strong>fold</strong></li><li>reduce requires an <strong>identity</strong> and an <strong>associative</strong> accumulator, the definition of a <strong>monoid</strong>, because that&#39;s what lets a stream be split and recombined. A false identity gave 38 sequentially and 88 in parallel</li><li>Streams are <strong>lazy</strong>, following Landin’s streams (1965) and the lazy evaluators of 1976. Nothing runs without a terminal operation, and elements pass through one at a time, which avoids intermediate structures in the spirit of Wadler’s <strong>deforestation</strong></li><li>A Java stream is a <strong>one-shot cursor</strong>, not a reusable lazy list, which is why it can be consumed only once</li><li><strong>Records</strong> are nominal tuples, Java’s form of a <strong>product type</strong>, finalized by JEP 395</li><li>groupingBy with a downstream collector is SQL&#39;s <strong>GROUP BY with an aggregate</strong>, implemented as a <strong>mutable reduction</strong>, and like an unordered GROUP BY, its result has no guaranteed order</li><li><strong>Optional</strong> is Java&#39;s version of ML&#39;s option and Haskell&#39;s Maybe, a response to Hoare&#39;s null reference, and its map follows the <strong>functor</strong> pattern Moggi and Wadler put at the center of functional programming</li><li>orElse is <strong>call-by-value</strong>; orElseGet takes a <strong>thunk</strong> and is <strong>call-by-need</strong>. The type system can&#39;t see the difference, because Java&#39;s types don&#39;t track <strong>effects</strong></li></ul><h3>The Arbiter Journey Continues</h3><p>One concept at a time, this series is building toward <strong>Arbiter</strong>, a microservices-based test management platform using Java, Spring Boot, databases, testing frameworks, Docker, and AWS. Today’s theory lines up closely with how it will be built. Summarizing test results into pass, fail, and skipped counts per suite is a GROUP BY with an aggregate, whether it&#39;s written as a groupingBy collector in Java or as a query against the database, and knowing they&#39;re the same operation makes it easier to decide which side should do the work. Spring Data repositories commonly return Optional from lookups, which is the &quot;return type for a possibly missing result&quot; use the Javadoc describes. Records are a natural fit for request and response objects that are only data. And the evaluation-strategy gap matters directly: a fallback that eagerly runs a query on every lookup has the same type as one that runs it only when needed, so the only protection is understanding when code actually runs.</p><h3>Watch the complete concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FrCDQ-YLfLIY%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DrCDQ-YLfLIY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/0a0ce11d2aea5b1bad0c8731577c6cfa/href">https://medium.com/media/0a0ce11d2aea5b1bad0c8731577c6cfa/href</a></iframe><h3>Follow along with the complete Arbiter playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><h3>GitHub Repository</h3><p>You can follow the complete learning journey, source code, explanations, and future lessons here:</p><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Follow the Complete Arbiter Series</h3><p>Every Guide and Guide Theory article in this series, collected in one place:</p><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Explore My Other Learning Series</h3><p>If you’re interested in my other programming and technology learning journeys, you may also enjoy:</p><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next?</h3><p>In <strong>Day 15</strong>, we move into <strong>Multithreading</strong>: Thread and Runnable. Today&#39;s parallel reduce was a first glimpse of the problem: once work is split across threads, the order things happen in is no longer fixed, and rules like associativity stop being optional. The theory goes back to the 1960s again, to the first attempts to reason about programs that run at the same time.</p><p>Companion guide article: For the hands-on walkthrough of this lesson, see the <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1"><strong><em>Day 14 Guide</em></strong></a>.</p><p>Happy coding! ☕</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a680e8492df3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/understanding-java-streams-the-theory-behind-lazy-evaluation-folds-and-the-optional-type-a680e8492df3">Understanding Java Streams — The Theory Behind Lazy Evaluation, Folds, and the Optional Type</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Java from Scratch with Arbiter — Streams API: Lazy Pipelines, reduce, groupingBy, and…]]></title>
            <link>https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/ac48e73d15d1</guid>
            <category><![CDATA[day-14]]></category>
            <category><![CDATA[optionals]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[stream]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 13:11:42 GMT</pubDate>
            <atom:updated>2026-10-30T18:56:40.353Z</atom:updated>
            <content:encoded><![CDATA[<h3>Learning Java from Scratch with Arbiter — Streams API: Lazy Pipelines, reduce, groupingBy, and Optional</h3><h4><em>Day 14 of my Arbiter Learning Journey | Why a stream does nothing until a terminal operation asks, how groupingBy can total while it groups, and why orElse runs its fallback even when nothing is missing</em></h4><figure><img alt="Word tiles flowing through a pipeline of filter, map, and collect stations, transaction cards sorted into Groceries and Electronics bins with totals, and an Optional capsule with an orElseGet fallback beside it." src="https://cdn-images-1.medium.com/max/1024/1*u_lL80DzY1hfeDxW4CQSnw.png" /><figcaption><em>A stream is a plan for processing data. Nothing moves until something at the end asks for a result.</em></figcaption></figure><p>Before we begin, here’s the source code for today’s lesson:</p><pre>import java.util.List;<br>import java.util.Map;<br>import java.util.Optional;<br>import java.util.stream.Collectors;<br><br>public class java_14StreamsOptional {<br>    public static void main(String[] args) {<br>        List&lt;String&gt; words = List.of(&quot;apple&quot;, &quot;kiwi&quot;, &quot;banana&quot;, &quot;fig&quot;, &quot;cherry&quot;, &quot;date&quot;);<br>        <br>        String str = words.stream()<br>            .filter(word -&gt; word.length() &gt; 3)<br>            .map(String::toUpperCase)<br>            .collect(Collectors.joining(&quot;, &quot;));<br>        <br>        int total = words.stream()<br>            .mapToInt(String::length)<br>            .reduce(0, Integer::sum);<br>        <br>        System.out.println(str);<br>        System.out.println(&quot;Total characters: &quot; + total);<br>        System.out.println();<br><br>        List&lt;Transaction&gt; transactions = List.of(<br>            new Transaction(&quot;Alice&quot;, &quot;Groceries&quot;, 45.20),<br>            new Transaction(&quot;Bob&quot;, &quot;Electronics&quot;, 299.99),<br>            new Transaction(&quot;Alice&quot;, &quot;Electronics&quot;, 150.00),<br>            new Transaction(&quot;Carol&quot;, &quot;Groceries&quot;, 60.00),<br>            new Transaction(&quot;Alice&quot;, &quot;Groceries&quot;, 12.50),<br>            new Transaction(&quot;Bob&quot;, &quot;Groceries&quot;, 22.30)<br>        );<br>        <br>        Map&lt;String, List&lt;Transaction&gt;&gt; customerTransactions = transactions.stream()<br>            .collect(Collectors.groupingBy(Transaction::customer));<br><br>        System.out.println(&quot;--- Customer Totals ---&quot;);<br>        System.out.printf(&quot;Alice = %.2f%n&quot;, totalFor(customerTransactions, &quot;Alice&quot;));<br>        System.out.printf(&quot;Bob = %.2f%n&quot;, totalFor(customerTransactions, &quot;Bob&quot;));<br>        System.out.printf(&quot;Dave = %.2f%n&quot;, totalFor(customerTransactions, &quot;Dave&quot;));<br><br>        Map&lt;String, Double&gt; categoryTotals = transactions.stream()<br>            .collect(Collectors.groupingBy(<br>                Transaction::category,<br>                Collectors.summingDouble(Transaction::amount)<br>            ));<br><br>        System.out.println(&quot;\n--- Category Totals ---&quot;);<br>        categoryTotals.forEach((category, totalCount) -&gt; <br>            System.out.printf(&quot;%s = %.2f%n&quot;, category, totalCount)<br>        );<br>    }<br>    <br>    record Transaction(String customer, String category, double amount) {}<br><br>    public static double totalFor(Map&lt;String, List&lt;Transaction&gt;&gt; byCustomer, String customer) {<br>        return Optional.ofNullable(byCustomer.get(customer))<br>            .map(list -&gt; list.stream()<br>            .mapToDouble(Transaction::amount)<br>            .sum()<br>        ).orElseGet(() -&gt; {<br>            System.out.println(&quot;No transactions found for &quot; + customer);<br>            return 0.0;<br>        });<br>    }<br>}</pre><p>Last time, lambdas and method references turned behavior into values you could store, pass, and combine. Today, those values get somewhere to go. The <strong>Streams API</strong> takes a collection and runs it through a chain of operations, like filter, map, reduce, and collect, where every step is just a lambda or a method reference from Day 13. And <strong>Optional</strong> gives a &quot;maybe there&#39;s a value here&quot; result a proper type, instead of a null waiting to blow up.</p><p>The interesting part isn’t how short the code gets. It’s three questions hiding behind it. <em>When does a stream actually do its work?</em> <em>How do you turn a list of records into totals per group in one pass?</em> And <em>what’s the real difference between two methods, </em><em>orElse and </em><em>orElseGet, that return exactly the same value?</em></p><p>This is <strong>Day 14</strong> of my <strong>Arbiter Learning Journey</strong>, where I’m learning Java from the ground up before eventually building <strong>Arbiter</strong>, a complete microservices-based test management platform.</p><p>In <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9"><strong><em>Day 13</em></strong></a>, I covered lambdas, functional interfaces, method references, and composing behavior with .and() and .andThen(), along with how a fallback can quietly swallow the reason something failed. If you want the theory behind it (lambda calculus, target typing, and how lambdas compile), the <a href="https://medium.com/codex/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9">Day 13 Guide Theory article</a> covers it.</p><h3>Today’s Topics</h3><ul><li><strong>Stream pipelines</strong>: a source, intermediate operations, and one terminal operation</li><li><strong>Intermediate operations</strong>: filter and map, and why they&#39;re lazy</li><li><strong>Terminal operations</strong>: collect(Collectors.joining(...)) and reduce(...)</li><li><strong>Primitive streams</strong>: mapToInt and mapToDouble</li><li><strong>Grouping</strong>: Collectors.groupingBy, with and without a downstream collector</li><li><strong>Records</strong>: a compact data class, and why Transaction::customer works</li><li><strong>Optional</strong>: ofNullable, map, and orElseGet versus orElse</li></ul><h3>A Stream Is a Plan Until a Terminal Operation Runs It</h3><p>The lesson opens with a pipeline over six words:</p><pre>List&lt;String&gt; words = List.of(&quot;apple&quot;, &quot;kiwi&quot;, &quot;banana&quot;, &quot;fig&quot;, &quot;cherry&quot;, &quot;date&quot;);<br><br>String str = words.stream()<br>    .filter(word -&gt; word.length() &gt; 3)<br>    .map(String::toUpperCase)<br>    .collect(Collectors.joining(&quot;, &quot;));</pre><p>Every stream pipeline has the same three parts:</p><ul><li><strong>A source</strong>: words.stream() turns the list into a stream of its elements</li><li><strong>Intermediate operations</strong>: filter keeps only words longer than 3 characters, and map turns each one into upper case. Each returns a <em>new stream</em></li><li><strong>A terminal operation</strong>: collect(Collectors.joining(&quot;, &quot;)) produces an actual result, here one String with the words joined by commas</li></ul><p>The lambdas are exactly the ones from Day 13. word -&gt; word.length() &gt; 3 is a Predicate&lt;String&gt;, and String::toUpperCase is a Function&lt;String, String&gt; written as a method reference.</p><p>Output:</p><pre>APPLE, KIWI, BANANA, CHERRY, DATE</pre><p>fig is the only word with 3 characters or fewer, so it&#39;s the only one missing.</p><p>Here’s the part that’s easy to miss. filter and map don&#39;t <em>do</em> anything when they&#39;re called. They only record a step. The work happens when the terminal operation asks for results. I checked this by adding a print inside each lambda and running the pipeline twice: once ending at .map(...), and once ending at .collect(...):</p><pre>-- no terminal op<br>-- with terminal op<br>filter apple<br>map apple<br>filter kiwi<br>map kiwi<br>filter banana<br>map banana<br>filter fig<br>filter cherry<br>map cherry<br>filter date<br>map date<br>APPLE, KIWI, BANANA, CHERRY, DATE</pre><p>Without a terminal operation, the pipeline compiled, ran, and printed nothing. Not a single lambda was called. With one, two more things become visible:</p><ul><li><strong>Elements go through one at a time</strong>, not one stage at a time. apple passes through filter <em>and</em> map before kiwi even reaches filter. The stream isn&#39;t building a filtered list and then mapping it.</li><li><strong>fig never reaches </strong><strong>map.</strong> It fails the filter, so toUpperCase is never called on it.</li></ul><p>That’s what “lazy” means here: the stream does exactly the work the terminal operation needs, and no more.</p><p>One more rule from the same check: a stream can only be used once. Calling a second terminal operation on the same stream object throws:</p><pre>java.lang.IllegalStateException: stream has already been operated upon or closed</pre><p>That’s why the lesson calls words.stream() again for the next pipeline instead of reusing the first one.</p><h3>reduce: Folding a Stream Down to One Value</h3><p>The second pipeline adds up the length of every word:</p><pre>int total = words.stream()<br>    .mapToInt(String::length)<br>    .reduce(0, Integer::sum);</pre><p>mapToInt(String::length) turns the Stream&lt;String&gt; into an IntStream, a stream of plain int values instead of boxed Integer objects. Then reduce(0, Integer::sum) folds them into one number. It starts with 0, the <strong>identity</strong>, and combines it with each element in turn using Integer::sum:</p><pre>0 + 5 (apple) = 5<br>5 + 4 (kiwi) = 9<br>9 + 6 (banana) = 15<br>15 + 3 (fig) = 18<br>18 + 6 (cherry) = 24<br>24 + 4 (date) = 28</pre><p>Output:</p><pre>Total characters: 28</pre><p>Notice that fig counts here. This is a fresh stream over the original, unfiltered words list. The first pipeline didn&#39;t change words at all. Streams never modify their source, and two pipelines over the same list don&#39;t share anything.</p><p>IntStream also has .sum(), which does the same thing in one call. Writing it as reduce shows what sum actually is: a fold with 0 as the starting value and + as the combining step.</p><h3>groupingBy: Grouping and Totaling in One Pass</h3><p>The second half of the lesson works with transactions. Each one is a <strong>record</strong>:</p><pre>record Transaction(String customer, String category, double amount) {}</pre><p>A record is a compact, immutable data class. That one line gives you a constructor, private final fields, and an accessor for each component, named after the component itself: customer(), category(), and amount(). There&#39;s no get prefix, which is why the method references below read as Transaction::customer rather than Transaction::getCustomer.</p><p>Six transactions go into a list, and the first grouping splits them by customer:</p><pre>Map&lt;String, List&lt;Transaction&gt;&gt; customerTransactions = transactions.stream()<br>    .collect(Collectors.groupingBy(Transaction::customer));</pre><p>groupingBy takes a <strong>classifier</strong>, a function that says which group each element belongs to. Here, Transaction::customer puts every transaction in a bucket under its customer&#39;s name. The result is a Map&lt;String, List&lt;Transaction&gt;&gt;: Alice gets three transactions, Bob gets two, and Carol gets one.</p><p>The second grouping does more in the same call:</p><pre>Map&lt;String, Double&gt; categoryTotals = transactions.stream()<br>    .collect(Collectors.groupingBy(<br>        Transaction::category,<br>        Collectors.summingDouble(Transaction::amount)<br>    ));</pre><p>The second argument is a <strong>downstream collector</strong>. It decides what each bucket holds. Without it, each bucket is a List of the matching elements. With Collectors.summingDouble(Transaction::amount), each bucket is a running total instead, so the result is Map&lt;String, Double&gt; directly. There&#39;s no separate step afterward that loops over the lists to add them up.</p><p>Tracing the data by hand:</p><ul><li><strong>Groceries</strong>: 45.20 + 60.00 + 12.50 + 22.30 = 140.00</li><li><strong>Electronics</strong>: 299.99 + 150.00 = 449.99</li></ul><p>Output:</p><pre>--- Category Totals ---<br>Groceries = 140.00<br>Electronics = 449.99</pre><p>One thing worth knowing: groupingBy returns a HashMap (I printed getClass() to check), so it makes no promise about the order the categories come out in. Groceries printing first here isn&#39;t something to rely on. If order matters, groupingBy has an overload that takes a map factory, like TreeMap::new.</p><figure><img alt="A two-panel infographic showing groupingBy collecting transactions into lists per customer on the left, and into summed totals per category on the right using a downstream collector." src="https://cdn-images-1.medium.com/max/1024/1*sJl-0uMJu5m-1WIUtErlLA.png" /><figcaption><em>The classifier decides which bucket. The downstream collector decides what goes in it.</em></figcaption></figure><h3>Optional: Handling a Missing Value Without a null Check</h3><p>Totals per customer go through a helper method:</p><pre>public static double totalFor(Map&lt;String, List&lt;Transaction&gt;&gt; byCustomer, String customer) {<br>    return Optional.ofNullable(byCustomer.get(customer))<br>        .map(list -&gt; list.stream()<br>        .mapToDouble(Transaction::amount)<br>        .sum()<br>    ).orElseGet(() -&gt; {<br>        System.out.println(&quot;No transactions found for &quot; + customer);<br>        return 0.0;<br>    });<br>}</pre><p>byCustomer.get(customer) returns the customer&#39;s list if they&#39;re in the map, and null if they aren&#39;t. Dave never appears in the data, so for him it&#39;s null. Instead of writing if (list == null), the method wraps the result:</p><ul><li><strong>Optional.ofNullable(...)</strong> produces an Optional holding the list if there is one, or an <em>empty</em> Optional if the value was null</li><li><strong>.map(...)</strong> runs only if a value is present. It turns the list into its summed amount (with mapToDouble and sum, a stream inside the Optional). If the Optional is empty, map skips the lambda and passes the empty Optional along</li><li><strong>.orElseGet(...)</strong> unwraps the result. If there&#39;s a value, it returns it. If not, it calls the Supplier lambda, which prints a message and returns 0.0</li></ul><p>Output:</p><pre>--- Customer Totals ---<br>Alice = 207.70<br>Bob = 322.29<br>No transactions found for Dave<br>Dave = 0.00</pre><p>The “No transactions found” line appears exactly once, and only for Dave. That’s the proof the Supplier ran only when it was needed. For Alice and Bob, it was never called.</p><p>There’s no isPresent() and no get() anywhere in this method. That&#39;s the point. Optional is meant to be used through methods like map and orElseGet, not as a null check with extra steps.</p><h3>Running It</h3><p>Here’s the complete output of the program, exactly as it ran:</p><pre>APPLE, KIWI, BANANA, CHERRY, DATE<br>Total characters: 28<br><br>--- Customer Totals ---<br>Alice = 207.70<br>Bob = 322.29<br>No transactions found for Dave<br>Dave = 0.00<br>--- Category Totals ---<br>Groceries = 140.00<br>Electronics = 449.99</pre><p>Reading it in groups:</p><ul><li><strong>Line 1</strong>: a lazy filter → map → collect pipeline over the words</li><li><strong>Line 2</strong>: mapToInt and reduce adding up every word&#39;s length, unfiltered</li><li><strong>Lines 4–8</strong>: groupingBy by customer, with Optional handling the customer who isn&#39;t there</li><li><strong>Lines 10–12</strong>: groupingBy by category with a summingDouble downstream collector</li></ul><h3>The Quiet Trap: orElse Runs Its Fallback Every Time</h3><p>Optional has two methods that look interchangeable: orElse(value) and orElseGet(supplier). Both return the value if there is one, and the fallback if there isn&#39;t. So I swapped the lesson&#39;s orElseGet for orElse, with the fallback moved into a method that prints the same message:</p><pre>static double fallback(String c) {<br>    System.out.println(&quot;No transactions found for &quot; + c);<br>    return 0.0;<br>}<br><br>double t = Optional.ofNullable(m.get(c))<br>    .map(l -&gt; l.stream().mapToDouble(Transaction::amount).sum())<br>    .orElse(fallback(c));</pre><p>Here’s what printed:</p><pre>No transactions found for Alice<br>Alice = 207.70<br>No transactions found for Bob<br>Bob = 322.29<br>No transactions found for Dave<br>Dave = 0.00</pre><p>Every total is still correct. Alice still gets 207.70, Bob still gets 322.29. But the program now claims Alice and Bob have no transactions, right before printing their totals.</p><p>The reason is ordinary Java, not anything special about Optional. orElse takes a <strong>value</strong>, and Java evaluates a method&#39;s arguments <em>before</em> the method runs. So fallback(c) is called for every customer, every time, whether or not the Optional is empty. Its return value is then thrown away whenever there was a real total. orElseGet takes a <strong>Supplier</strong>, a lambda, and only calls it when the Optional is actually empty.</p><p>That’s what makes it quiet. Nothing fails, and the numbers are right. Here the side effect is a misleading log line. In a real system, it could be a database query, a network call, or a new object created for every lookup and discarded every time. The rule of thumb:</p><ul><li><strong>orElse(...)</strong> for a fallback that&#39;s a plain value or already exists, like 0.0 or a constant</li><li><strong>orElseGet(...)</strong> for a fallback that does work, has side effects, or costs something to build</li></ul><p>It’s the same family as Day 13’s swallowed exception: everything behaves correctly on paper, and the cost hides in a place you don’t think to look.</p><h3>Key Takeaway</h3><p>Streams describe what should happen to data, and the terminal operation decides when it actually does:</p><ul><li>A pipeline is <strong>source → intermediate operations → terminal operation</strong>. Without a terminal operation, nothing runs</li><li>Intermediate operations are <strong>lazy</strong>, and elements pass through the whole pipeline <strong>one at a time</strong></li><li>A stream can be used <strong>only once</strong>, and it never modifies its source</li><li><strong>reduce</strong> folds a stream into one value from an identity and a combining function</li><li><strong>groupingBy</strong> splits elements into buckets by a classifier, and a <strong>downstream collector</strong> decides what each bucket holds</li><li><strong>Optional</strong> replaces null checks with map and orElseGet, and <strong>orElse evaluates its fallback even when the value is present</strong></li></ul><p>And like the last few lessons, the dangerous case is the one that runs without complaint. Two methods that return the same answer can do very different amounts of work to get there.</p><h3>The Arbiter Journey Continues</h3><p>One concept at a time, this series is building toward <strong>Arbiter</strong>, a microservices-based test management platform using Java, Spring Boot, databases, testing frameworks, Docker, and AWS. Today’s lesson is close to code Arbiter will actually contain. Turning a list of test results into pass, fail, and skipped counts per suite is a groupingBy with a downstream collector, the same shape as the category totals here. Converting entities into API response objects is a map over a stream. Spring Data JPA repositories commonly return Optional from lookups like findById, so &quot;test case not found&quot; becomes an empty Optional rather than a null. And the orElse trap matters directly: a fallback that quietly runs a query on every lookup is exactly the kind of cost that only shows up once a service is under load.</p><h3>Watch the complete concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FrCDQ-YLfLIY%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DrCDQ-YLfLIY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/0a0ce11d2aea5b1bad0c8731577c6cfa/href">https://medium.com/media/0a0ce11d2aea5b1bad0c8731577c6cfa/href</a></iframe><h3>Follow along with the complete Arbiter playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><h3>GitHub Repository</h3><p>You can follow the complete learning journey, source code, explanations, and future lessons here:</p><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Follow the Complete Arbiter Series</h3><p>Every Guide and Guide Theory article in this series, collected in one place:</p><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Explore My Other Learning Series</h3><p>If you’re interested in my other programming and technology learning journeys, you may also enjoy:</p><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next?</h3><p>In <strong>Day 15</strong>, we move into <strong>Multithreading</strong>: Thread and Runnable. So far, every program in this series has done one thing at a time, in order. Next, Java starts running work side by side, and Runnable, a functional interface with a single run() method, means today&#39;s lambdas are already the way to hand that work to a thread.</p><p>Happy coding! ☕</p><p><strong>Companion theory article:</strong> For the theoretical background behind this lesson, see the Day 14 Guide Theory piece, published alongside this one. For the previous lesson’s theory, see <a href="https://medium.com/codex/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9">Day 13 Guide Theory</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ac48e73d15d1" width="1" height="1" alt=""><hr><p><a href="https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-streams-api-lazy-pipelines-reduce-groupingby-and-ac48e73d15d1">Learning Java from Scratch with Arbiter — Streams API: Lazy Pipelines, reduce, groupingBy, and…</a> was originally published in <a href="https://blog.devgenius.io">Dev Genius</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[13 Days of Learning Java in Public: What’s Working, What I’m Changing, and What I’d Like Your Take…]]></title>
            <link>https://blog.devgenius.io/13-days-of-learning-java-in-public-whats-working-what-i-m-changing-and-what-i-d-like-your-take-9d5e505437b3?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/9d5e505437b3</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[learning-in-public]]></category>
            <category><![CDATA[arbiter]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Fri, 23 Oct 2026 13:54:43 GMT</pubDate>
            <atom:updated>2026-10-23T15:51:15.760Z</atom:updated>
            <content:encoded><![CDATA[<h3>13 Days of Learning Java in Public: What’s Working, What I’m Changing, and What I’d Like Your Take On</h3><p><em>An Arbiter Learning Journey check-in | Five questions about format, depth, and code, plus what should come after the series, before it moves into Streams</em></p><figure><img alt="A dark-mode, minimalist illustration from a top-down view of a developer’s desk on a dark slate-blue background. At the center is an open laptop with its screen glowing in emerald and sky-blue, displaying an abstract Java code editor. Transparent, glowing speech-bubble shapes rise up from the screen. On the right, an open wire-bound notebook shows a vertical checklist with exactly thirteen ticked boxes next to illegible handwritten scribbles, with a pen resting beside it." src="https://cdn-images-1.medium.com/max/1024/1*yvYlNBpSNo0akOe8T9pgbw.png" /><figcaption><strong><em>A workspace of reflection:</em></strong><em> Thirteen lessons down, and a whole new chapter to write. Before the next one begins, I’d love to hear your thoughts, experiences, and feedback in the comments. Let’s discuss!</em></figcaption></figure><p>Thirteen days ago I started learning Java from scratch, in public, with one goal: eventually build <strong>Arbiter</strong>, a microservices-based test management platform using Java, Spring Boot, databases, testing frameworks, Docker, and AWS.</p><p>Every lesson so far has produced two articles. The <strong>Guide</strong> walks through the code: what it does, what it prints, and where it surprised me. The <strong>Guide Theory</strong> piece asks why it behaves that way, and traces each idea back to the paper, person, or specification it came from.</p><p>This post is different. There’s no new lesson and no source file. Before the series moves into the Streams API, I want to step back, say what I think is working and what isn’t, and ask you a few direct questions. If you’ve read even one article in the series, your answer is useful to me.</p><h3>What I Think Is Working</h3><p><strong>Verified output, not predicted output.</strong> Every output block in the series comes from actually compiling and running the code. When an article claims something about the compiler, I try to show the compiler saying it: the exact error message, or a few lines of javap output. Writing it this way has caught more of my own wrong assumptions than I&#39;d like to admit.</p><p><strong>The Quiet Trap.</strong> Each lesson ends with one behavior that runs without complaint and is still wrong: a HashMap tie-break that depended on iteration order, an unchecked exception nothing forced anyone to handle, a fallback that kept the program alive but threw away the reason it failed. These have become my favorite part to write, because they&#39;re the bugs that actually reach production.</p><p><strong>Theory with names attached.</strong> Instead of “historically, exceptions were introduced to…”, the Theory articles try to point at the actual source: Goodenough’s 1975 paper on exception handling, Church’s lambda calculus, Backus’s Turing lecture on composition. I think that makes the ideas easier to remember. You may disagree, which is one of my questions below.</p><h3>What I’m Changing</h3><p><strong>Being upfront when the roadmap shifts.</strong> If you’ve been following the “What’s Next” sections closely, you’ll have noticed that Sets have been previewed and then postponed more than once. The lesson order follows the source files I’m working through, and that order hasn’t always matched what I previewed. From here on, “What’s Next” is a plan, and when the plan changes I’ll say so at the top of the article rather than in a side note.</p><p><strong>Consistency across articles.</strong> Link formats and closing sections have drifted a little from day to day. I’m tidying that up so every article in the series looks and navigates the same way.</p><h3>Five Questions for You</h3><p>These are the decisions I keep going back and forth on. You don’t need to answer all of them. Reply with the number and a sentence, and that already helps.</p><p><strong>1. Two articles per lesson, or one?</strong> Is the Guide / Theory split useful, or would you rather read one longer article that covers both? Do you read both, or just one of them?</p><p><strong>2. Is the theory depth right?</strong> The Theory articles go into history, specifications, and bytecode. Is that the reason you read them, or is it more than you need to understand the lesson?</p><p><strong>3. Full code, or key excerpts?</strong> Right now the complete source file appears at the top of both articles. Would you prefer just the relevant snippets inline, with the full file linked on GitHub?</p><p><strong>4. Length.</strong> Is a typical Guide article too long, too short, or about right? If you stopped partway through one, where did you stop?</p><p><strong>5. What should come next, or alongside?</strong> When Arbiter wraps up, or as something to read between lessons, which of these would you actually read?</p><ul><li>Short standalone “Quiet Trap” posts: one Java gotcha, one fix, five minutes</li><li>Spring Boot internals, explained the same way as this series</li><li>Java for testing: JUnit, test design, and building test automation</li><li>Something else (tell me what)</li></ul><h3>How to Answer</h3><p>Leave a comment with the question number and your answer, even if it’s one line. Disagreement is especially welcome. If you think a whole section of the format should go, that’s exactly what I want to hear before I write the next thirteen days the same way.</p><p>I’ll read every reply, and I’ll write up what changes as a result in a future post.</p><h3>The Arbiter Journey Continues</h3><p>The next lesson, Day 14, moves into the Streams API: map, filter, reduce, and collect, built out of the Predicates, Functions, and method references from Day 13.</p><p>If you’re new here, these are the best places to start:</p><p><strong>YouTube playlist:</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><p><strong>GitHub repository:</strong></p><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><p><strong>Arbiter Guide:</strong></p><p><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></p><p><strong>Arbiter Guide Theory:</strong></p><p><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></p><p>Happy coding! ☕</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d5e505437b3" width="1" height="1" alt=""><hr><p><a href="https://blog.devgenius.io/13-days-of-learning-java-in-public-whats-working-what-i-m-changing-and-what-i-d-like-your-take-9d5e505437b3">13 Days of Learning Java in Public: What’s Working, What I’m Changing, and What I’d Like Your Take…</a> was originally published in <a href="https://blog.devgenius.io">Dev Genius</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Java Lambdas — The Theory Behind Lambda Calculus, Functional Interfaces, and Method…]]></title>
            <link>https://medium.com/codex/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/6cce70a4beb9</guid>
            <category><![CDATA[13days]]></category>
            <category><![CDATA[theory]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[lambda]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 05:02:57 GMT</pubDate>
            <atom:updated>2026-10-29T09:21:25.335Z</atom:updated>
            <content:encoded><![CDATA[<h3>Understanding Java Lambdas — The Theory Behind Lambda Calculus, Functional Interfaces, and Method References</h3><h4>Arbiter Guide Theory • Day 13 | Where the arrow came from, why Java gave lambdas interfaces instead of function types, why Function can&#39;t throw a checked exception, and what the bytecode reveals about method references</h4><figure><img alt="A blue wireframe lambda symbol connected by an arrow to an interface with a single abstract-method slot, alongside a composition pipeline and faint invokedynamic bytecode lines." src="https://cdn-images-1.medium.com/max/1024/1*gmGE9WJDaGEbpVf3afMa1A.png" /><figcaption><em>A lambda is an idea from 1930s logic. A functional interface is how Java gave it a type. Composition is how small functions become one decision.</em></figcaption></figure><p>Here’s the source code for today’s lesson:</p><pre>import java.util.function.Function;<br>import java.util.function.Predicate;<br><br>class Employee {<br>    private String name;<br>    private double salary;<br><br>    public Employee(String name, double salary) {<br>        this.name = name;<br>        this.salary = salary;<br>    }<br><br>    public String getName() { return name; }<br>    public void setName(String name) { this.name = name; }<br><br>    public double getSalary() { return salary; }<br>    public void setSalary(double salary) { this.salary = salary; }<br><br>    public boolean isNameNonBlank() {<br>        return name != null &amp;&amp; !name.trim().isEmpty();<br>    }<br><br>    // Static method used for method reference<br>    public static boolean isSalaryAboveThreshold(Employee employee) {<br>        return employee.getSalary() &gt; 60000.0;<br>    }<br>}<br><br>class FormatException extends Exception {<br>    public FormatException(String message) {<br>        super(message);<br>    }<br>}<br><br>@FunctionalInterface<br>interface EmployeeFormatter {<br>    String format(Employee employee) throws FormatException;<br><br>    default String formatOrDefault(Employee employee, String fallback) {<br>        try {<br>            return format(employee);<br>        } catch (FormatException e) {<br>            return fallback;<br>        }<br>    }<br>}<br><br>public class java_13Lambdas {<br>    public static void main(String[] args) {<br>        String[] usernames = new String[] {<br>            &quot;aayush99&quot;, &quot;bob&quot;,<br>            &quot;x&quot;, &quot;claude_code&quot;,<br>            &quot;jd&quot;, &quot;ninacodes&quot;<br>        };<br><br>        Predicate&lt;String&gt; correctUsername = str -&gt; str.length() &gt;= 5;<br>        for (int i = 0; i &lt; usernames.length; i++) {<br>            if (correctUsername.test(usernames[i])) System.out.println(usernames[i]);<br>        }<br>        System.out.println();<br><br>        Employee[] employees = new Employee[] {<br>            new Employee(&quot;Alice Kim&quot;, 72000.0),<br>            new Employee(&quot;bob jones&quot;, 45000.0),<br>            new Employee(&quot;CHARLIE NG&quot;, 138000.0),<br>            new Employee(&quot;dana park&quot;, 61000.0),<br>            new Employee(&quot;Evan Osei&quot;, 29500.0),<br>            // new Employee(null, 61000.0), // Uncommenting this record will trigger exception<br>        };<br><br>        // Arbitrary-object-instance method reference form (Class::instanceMethod)<br>        Predicate&lt;Employee&gt; nameCheck = Employee::isNameNonBlank; // Used here<br>        Predicate&lt;Employee&gt; salaryCheck = Employee::isSalaryAboveThreshold; // Static method reference form<br>        <br>        // Composed Predicate for Bonus Eligibility<br>        Predicate&lt;Employee&gt; bonusEligible = salaryCheck.and(nameCheck);<br><br>        EmployeeFormatter reportLineFormatter = emp -&gt; {<br>            if (emp.getName() == null) throw new FormatException(&quot;Employee name cannot be null&quot;);<br><br>            // Arbitrary-object-instance method reference forms used here<br>            Function&lt;String, String&gt; normalize = String::trim;<br>            Function&lt;String, String&gt; uppercase = normalize.andThen(String::toUpperCase);<br>            <br>            String formattedName = uppercase.apply(emp.getName());<br>            return String.format(&quot;%s - %.1f&quot;, formattedName, emp.getSalary());<br>        };<br><br>        System.out.println(&quot;=== Bonus Eligible ===&quot;);<br>        for (int i = 0; i &lt; employees.length; i++) {<br>            Employee employee = employees[i];<br>            if (bonusEligible.test(employee)) System.out.println(reportLineFormatter.formatOrDefault(employee, &quot;INVALID_RECORD&quot;));<br>        }<br>        System.out.println();<br><br>        System.out.println(&quot;=== All Records ===&quot;);<br>        for (int i = 0; i &lt; employees.length; i++) {<br>            System.out.println(reportLineFormatter.formatOrDefault(employees[i], &quot;INVALID_RECORD&quot;));<br>        }<br>    }<br>}</pre><p>The <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9"><strong><em>Day 13 Guide</em></strong></a> walks through what this program does: a Predicate filtering usernames, a custom EmployeeFormatter that can throw a checked exception, two kinds of method reference, and two compositions. This article is about <em>why</em> it behaves the way it does.</p><p>Almost nothing in the lesson is new to computer science. The arrow notation descends from a logician’s notation for anonymous functions in the 1930s. The idea that functions can be passed around like numbers was named in a 1967 lecture. Combining small functions into bigger ones was the centerpiece of a 1978 Turing Award lecture. What <em>is</em> specific to Java is how all of this was bolted onto a language that had spent nearly two decades without it, and the compromises that bolting-on required. Those compromises are exactly where the lesson’s compiler errors come from.</p><p>If you missed the previous lesson, the <a href="https://medium.com/codex/understanding-java-generics-the-theory-behind-bounded-quantification-use-site-variance-and-the-e8a14870ff6c">Day 12 Guide Theory</a> covers the theory behind generics, and the <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-generics-bounded-type-parameters-wildcards-and-pecs-c6ff1a636cd8">Day 12 Guide</a> covers the code.</p><h3>Where the Arrow Comes From: Church’s Lambda Calculus and McCarthy’s LISP</h3><p><em>A function with no name, written as a value</em></p><h4>The 1930s notation for anonymous functions, and the 1960 language that made it executable</h4><p>The lesson’s first lambda is one line:</p><pre>Predicate&lt;String&gt; correctUsername = str -&gt; str.length() &gt;= 5;</pre><p>In ordinary mathematics, you name a function before you use it: <em>let f(x) = …</em>. Alonzo Church wanted a way to write the function itself, without naming it. His notation, which first appeared in “A Set of Postulates for the Foundation of Logic” (<em>Annals of Mathematics</em>, 1932) and became the basis of “An Unsolvable Problem of Elementary Number Theory” (<em>American Journal of Mathematics</em>, 58(2), 1936), wrote it as <strong>λx.M</strong>: “the function that takes x and produces M.&quot; That Greek letter is where the name <em>lambda</em> comes from.</p><p>Java’s str -&gt; str.length() &gt;= 5 is that same idea with different punctuation. str is the bound variable, -&gt; plays the role of the dot, and str.length() &gt;= 5 is the body. The function has no name. It only exists as a value assigned to correctUsername.</p><p>Church’s lambda calculus was a theory of computation on paper. John McCarthy brought it into a running language in “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I” (<em>Communications of the ACM</em>, 3(4), 1960), the paper that introduced LISP, and borrowed Church’s LAMBDA notation directly for anonymous functions.</p><p>The other half of the idea, that a function can be treated like any other value, got its name from Christopher Strachey’s 1967 lecture notes “Fundamental Concepts in Programming Languages” (later published in <em>Higher-Order and Symbolic Computation</em>, 13, 2000). Strachey called values that can be passed as arguments, returned, and stored in variables <strong>first-class citizens</strong>. Before Java 8, a Java method was not first-class. You couldn’t store isNameNonBlank in a variable. Today&#39;s lesson does exactly that.</p><h3>Target Typing: Why a Lambda Needs Exactly One Abstract Method</h3><p><em>Java chose interfaces over function types</em></p><h4>Project Lambda’s central decision, and the rule the compiler enforces</h4><p>Most languages with lambdas have a dedicated <strong>function type</strong>, something like (String) -&gt; boolean. Java doesn&#39;t. A Java lambda has no type of its own at all. It only gets a type from the context it&#39;s written in, which the Java Language Specification calls its <strong>target type</strong> (JLS §15.27.3).</p><p>That wasn’t the only design on the table. In 2006, Gilad Bracha, Neal Gafter, James Gosling, and Peter von der Ahé published the <strong>BGGA</strong> closures proposal, which added structural function types to Java. The design that actually shipped came from <strong>JSR 335</strong>, “Lambda Expressions for the Java Programming Language,” led by Brian Goetz and finalized with Java SE 8 in March 2014. Goetz’s design documents, published as the “State of the Lambda” series, explain the choice: Java’s libraries were already full of single-method interfaces like Runnable, Comparator, and ActionListener. Making lambdas target <em>those</em> meant every existing API that took such an interface would accept a lambda immediately, without anything being rewritten.</p><p>Those interfaces had an informal name in the community, <strong>SAM types</strong> (Single Abstract Method). JSR 335 formalized them as <strong>functional interfaces</strong> (JLS §9.8). The rule follows directly: if the target is an interface, the lambda’s body must be the implementation of <em>one</em> method. With two abstract methods, there’s no way to decide which one the body is for. The Guide showed the compiler saying so:</p><pre>error: incompatible types: Fmt is not a functional interface<br>    multiple non-overriding abstract methods found in interface Fmt</pre><p>And with the annotation from JLS §9.6.4.9, the same check moves to the declaration:</p><pre>error: Unexpected @FunctionalInterface annotation<br>  Fmt is not a functional interface<br>    multiple non-overriding abstract methods found in interface Fmt</pre><p>@FunctionalInterface changes nothing about what the interface <em>is</em>. Any interface with one abstract method is functional whether or not it&#39;s annotated. The annotation only asks the compiler to check the claim where it&#39;s made, rather than at some distant line where a lambda first tries to use it.</p><h3>Default methods, and why they don’t break the rule</h3><p>EmployeeFormatter has two methods, but only one is abstract:</p><pre>@FunctionalInterface<br>interface EmployeeFormatter {<br>    String format(Employee employee) throws FormatException;<br><br>    default String formatOrDefault(Employee employee, String fallback) { ... }<br>}</pre><p>Default methods came from the same JSR. Goetz’s 2011 draft “Interface Evolution via Virtual Extension Methods” described the problem they solved: Java 8 needed to add methods like forEach and stream to existing interfaces such as Collection without breaking every class that already implemented them. A default method carries its own body, so implementers don&#39;t have to write one. And because it already has a body, it doesn&#39;t compete with the lambda for &quot;the one method that needs implementing.&quot; That&#39;s why JLS §9.8 counts only abstract methods.</p><h3>Why Function Can&#39;t Throw a Checked Exception</h3><p><em>The gap between Java’s two type systems</em></p><h4>Exception transparency, and the feature that didn’t make it into Java 8</h4><p>The Guide showed this error:</p><pre>Function&lt;String, String&gt; f = s -&gt; {<br>    if (s == null) throw new FormatException(&quot;x&quot;);<br>    return s;<br>};</pre><pre>error: unreported exception FormatException; must be caught or declared to be thrown</pre><p>The rule behind it is JLS §11.2.3: a lambda body may throw a checked exception only if the <strong>function type</strong> it targets declares that exception (or a supertype) in its throws clause. Function.apply declares nothing. So a lambda targeting Function can throw only unchecked exceptions.</p><p>This is a collision between two separate systems in Java. Generics (Day 12) let an interface abstract over the <em>types</em> of its parameters and result. Checked exceptions (Day 11) are part of a method’s signature too, but there’s no ordinary way for a generic interface to say “and whatever checked exceptions the implementation throws.” The general fix is called <strong>exception transparency</strong>: letting an interface be parameterized by the exceptions its method can throw. The BGGA proposal included a mechanism for it, and exception transparency was discussed during JSR 335’s development, but it was not part of the design that shipped. The built-in interfaces in java.util.function were defined without throws clauses.</p><p>That leaves two options when behavior can fail in a checked way: catch and wrap inside the lambda, or define your own functional interface whose method declares the exception. EmployeeFormatter is the second option. It isn&#39;t a style preference. It&#39;s the type-system answer to a gap the built-ins deliberately leave open.</p><h3>Method References and η-Conversion</h3><p><em>x -&gt; f(x) is just </em><em>f</em></p><h4>What lambda calculus says, and what the bytecode confirms</h4><p>The lesson uses two method references that look the same but aren’t:</p><pre>Predicate&lt;Employee&gt; nameCheck = Employee::isNameNonBlank;<br>Predicate&lt;Employee&gt; salaryCheck = Employee::isSalaryAboveThreshold;</pre><p>Lambda calculus has a rule for exactly this situation, called <strong>η-conversion</strong> (eta): a function that does nothing except pass its argument to another function is equivalent to that other function. λx.(f x) is the same as f. A method reference (JLS §15.13) is Java&#39;s built-in η-conversion. e -&gt; Employee.isSalaryAboveThreshold(e) collapses to Employee::isSalaryAboveThreshold.</p><p>The instance form adds one twist. isNameNonBlank() takes no arguments, yet Predicate.test passes one in. The resolution is that an instance method has a hidden parameter: the object it&#39;s called on. Employee::isNameNonBlank treats the incoming Employee as that receiver, so e -&gt; e.isNameNonBlank() collapses to it.</p><p>That difference isn’t just a way of describing things. It shows up in the compiled class file. Running javap -v on the lesson&#39;s class lists the method handles each reference points to:</p><pre>REF_invokeStatic   Employee.isSalaryAboveThreshold:(LEmployee;)Z<br>REF_invokeVirtual  Employee.isNameNonBlank:()Z<br>REF_invokeVirtual  java/lang/String.trim:()Ljava/lang/String;<br>REF_invokeVirtual  java/lang/String.toUpperCase:()Ljava/lang/String;</pre><p>The static reference compiles to REF_invokeStatic with the Employee as an explicit parameter, (LEmployee;). The arbitrary-instance references compile to REF_invokeVirtual with no parameters at all, (), because the receiver isn&#39;t a parameter, it&#39;s the object the call is made on. The two &quot;forms&quot; from the Guide are literally two different kinds of JVM call.</p><h3>Composition: Backus’s Functional Forms</h3><p><em>Build programs by combining functions, not by writing steps</em></p><h4>The 1978 Turing lecture, and why .and() and .andThen() are combinators</h4><p>The lesson combines its behaviors in two lines:</p><pre>Predicate&lt;Employee&gt; bonusEligible = salaryCheck.and(nameCheck);<br>Function&lt;String, String&gt; uppercase = normalize.andThen(String::toUpperCase);</pre><p>In mathematics, chaining functions is written with the composition operator: <strong>(g ∘ f)(x) = g(f(x))</strong>. John Backus, the creator of FORTRAN, made composition the centerpiece of his 1977 Turing Award lecture, published as “Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs” (<em>Communications of the ACM</em>, 21(8), 1978). His argument was that programs should be built from a small set of <strong>functional forms</strong>, operations that take functions and return new functions, instead of from sequences of statements that update variables.</p><p>.and() and .andThen() are functional forms in exactly Backus&#39;s sense. Each takes a function and returns a <em>new</em> function, without running anything:</p><ul><li>normalize.andThen(String::toUpperCase) is toUpperCase ∘ normalize: trim first, then upper-case. (Java also has compose, which runs in the opposite order: f.compose(g) is f ∘ g, applying g first.)</li><li>salaryCheck.and(nameCheck) is the logical conjunction of two predicates. Its documentation specifies that it <strong>short-circuits</strong>, just like &amp;&amp;: if salaryCheck returns false, nameCheck is never called.</li></ul><p>That second point matters for correctness, not just speed. The order of .and() is the order of evaluation. In this program, the salary check doesn&#39;t touch the name and the name check is null-safe, so the order doesn&#39;t change the result. But a predicate that <em>assumed</em> a non-null name would have to come after the one that checks for it.</p><h3>How It Actually Runs: invokedynamic</h3><p><em>No anonymous classes, no extra class files</em></p><h4>JSR 292, the lambda metafactory, and Landin’s closures</h4><p>Before Java 8, the only way to pass behavior around was an <strong>anonymous inner class</strong>, and every one of them compiled to its own .class file on disk. Compiling today&#39;s lesson produces exactly four class files: java_13Lambdas.class, Employee.class, EmployeeFormatter.class, and FormatException.class. Six pieces of behavior, zero extra classes.</p><p>javap shows where they went. Each lambda and method reference in main is a single invokedynamic instruction:</p><pre>36: invokedynamic #21,  0   // InvokeDynamic #0:test:()Ljava/util/function/Predicate;<br>163: invokedynamic #69,  0  // InvokeDynamic #1:test:()Ljava/util/function/Predicate;<br>170: invokedynamic #70,  0  // InvokeDynamic #2:test:()Ljava/util/function/Predicate;<br>188: invokedynamic #75,  0  // InvokeDynamic #3:format:()LEmployeeFormatter;</pre><p>invokedynamic was added in Java 7 by <strong>JSR 292</strong>, &quot;Supporting Dynamically Typed Languages on the Java Platform,&quot; led by John Rose. It was originally meant for languages like JRuby. Java 8 reused it for lambdas: the first time each instruction runs, it calls a bootstrap method, java.lang.invoke.LambdaMetafactory.metafactory, which builds an object implementing the target interface at runtime.</p><p>The two arrow-syntax lambdas have bodies that need to live somewhere, so the compiler moved them into private methods of the class:</p><pre>private static java.lang.String lambda$main$1(Employee) throws FormatException;<br>private static boolean lambda$main$0(java.lang.String);</pre><p>Two details here connect back to the theory. First, lambda$main$1 carries throws FormatException in its signature, the checked exception from §11.2.3, preserved all the way into bytecode. Second, method references produce <em>no</em> generated method at all. They point straight at isNameNonBlank, trim, and the rest. That&#39;s η-conversion again: there&#39;s no wrapper function, because x -&gt; f(x) really is just f.</p><p>Both generated methods are also static. Peter Landin&#39;s &quot;The Mechanical Evaluation of Expressions&quot; (<em>The Computer Journal</em>, 6(4), 1964) introduced the term <strong>closure</strong> for a function bundled together with the variables it uses from its surroundings. Neither lambda in this lesson uses any outside variables, so there&#39;s nothing to bundle, and the compiler doesn&#39;t even need an instance to hold them.</p><h3>The Theory Gap: A Type System Can Force a Catch, Not a Read</h3><p><em>The quiet trap, viewed from the theory side</em></p><h4>Goodenough’s exception model, and what formatOrDefault throws away</h4><p>When the commented-out new Employee(null, 61000.0) line is restored, the program finishes normally:</p><pre>=== All Records ===<br>ALICE KIM - 72000.0<br>BOB JONES - 45000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0<br>EVAN OSEI - 29500.0<br>INVALID_RECORD</pre><p>Everything the type system asked for was done. The lambda declared FormatException, the default method caught it, and the compiler was satisfied. But the message the exception carried, &quot;Employee name cannot be null&quot;, is printed nowhere, because the catch block never reads e.</p><p>John Goodenough’s “Exception Handling: Issues and a Proposed Notation” (<em>Communications of the ACM</em>, 18(12), 1975), the paper Java’s exception model traces back to, treated an exception as a way for an operation to tell its caller <em>why</em> it couldn’t complete normally. The type is only half of that message. The other half is the information the exception carries. Joshua Bloch makes the same point as a rule in <em>Effective Java</em> (3rd ed., 2018), Item 77, “Don’t ignore exceptions”: an empty or uninformative handler defeats the purpose of having the exception at all.</p><p>That’s the limit of what checked exceptions can do. JLS §11.2.3 can force a lambda’s exception to be <em>caught</em> somewhere. It cannot force the handler to do anything useful with it. A fallback value like &quot;INVALID_RECORD&quot; is a legitimate design choice for a report, but at the type level, a handler that logs the reason and a handler that discards it look identical. The compiler can&#39;t tell them apart, so the difference has to come from the programmer.</p><h3>Why These Concepts Matter</h3><ul><li><strong>Lambda calculus</strong> (Church, 1932–1936) is where anonymous functions come from, and <strong>first-class functions</strong> (Strachey, 1967) is the idea that they can be stored and passed like values</li><li>Java lambdas have <strong>no type of their own</strong>. They take the type of a <strong>functional interface</strong>, a design JSR 335 chose so that existing single-method APIs would accept lambdas immediately</li><li><strong>Default methods</strong> don’t count toward the one-abstract-method rule, because they already have a body</li><li>The built-in interfaces <strong>can’t throw checked exceptions</strong> because Java 8 shipped without exception transparency. A custom functional interface is the type-system workaround</li><li>A <strong>method reference</strong> is η-conversion, and the static vs. instance distinction is visible in bytecode as REF_invokeStatic vs. REF_invokeVirtual</li><li>.and() and .andThen() are <strong>functional forms</strong> in Backus&#39;s sense: they combine functions into new functions without running them</li><li>Lambdas compile to <strong>invokedynamic</strong> and LambdaMetafactory, not anonymous classes</li><li>A checked exception can be forced to be <strong>caught</strong>, but not <strong>read</strong>. That gap is where the quiet trap lives</li></ul><h3>The Arbiter Journey Continues</h3><p>One concept at a time, this series is building toward <strong>Arbiter</strong>, a microservices-based test management platform using Java, Spring Boot, databases, testing frameworks, Docker, and AWS. The theory from today shows up throughout it. Spring’s APIs are full of functional interfaces, so configuration and callbacks are often a lambda away. Filtering test runs by several criteria is predicate composition, where the order of .and() decides which check runs first. Steps that call external services and can fail with checked exceptions are exactly where a custom functional interface, rather than a built-in one, earns its place. And the theory gap matters directly: a test platform&#39;s whole job is to explain failures, so a handler that discards the reason a step failed would undermine the reason Arbiter exists.</p><h3>Watch the full explaination into Lambdas</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FTGlpMeeKM08%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DTGlpMeeKM08&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FTGlpMeeKM08%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c0f9aeaf37f99e341cc1fe27369bdff/href">https://medium.com/media/3c0f9aeaf37f99e341cc1fe27369bdff/href</a></iframe><h3>Follow along with the complete Arbiter playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><h3>GitHub Repository</h3><p>You can follow the complete learning journey, source code, explanations, and future lessons here:</p><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Follow the Complete Arbiter Series</h3><p>Every Guide and Guide Theory article in this series, collected in one place:</p><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Explore My Other Learning Series</h3><p>If you’re interested in my other programming and technology learning journeys, you may also enjoy:</p><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next?</h3><p>In <strong>Day 14</strong>, we move into the <strong>Streams API</strong>: map, filter, reduce, and collect. The theory picks up right where today leaves off. map and reduce come straight from the functional-programming tradition Backus argued for, and streams add an idea today&#39;s compositions only hinted at: <strong>laziness</strong>, where nothing runs until a result is actually asked for.</p><p>Companion guide article: For the hands-on walkthrough of this lesson, see the <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9"><strong><em>Day 13 Guide</em></strong></a>.</p><p>Happy coding! ☕</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6cce70a4beb9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9">Understanding Java Lambdas — The Theory Behind Lambda Calculus, Functional Interfaces, and Method…</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Java from Scratch with Arbiter — Lambdas: Functional Interfaces, Method References, and…]]></title>
            <link>https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9?source=rss-ddefc37f42cb------2</link>
            <guid isPermaLink="false">https://medium.com/p/1dcf5bd07ad9</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[13days]]></category>
            <dc:creator><![CDATA[Someone]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 04:53:22 GMT</pubDate>
            <atom:updated>2026-10-29T09:19:57.792Z</atom:updated>
            <content:encoded><![CDATA[<h3>Learning Java from Scratch with Arbiter — Lambdas: Functional Interfaces, Method References, and Composition</h3><h4>Day 13 of my Arbiter Learning Journey | Why a lambda needs exactly one abstract method to land on, why the built-in interfaces can’t throw checked exceptions, and how a fallback can quietly swallow the reason something failed</h4><figure><img alt="A lambda symbol sliding into a single-slot keyhole on the left, and on the right a pipeline of Predicate and Function nodes joined by .and() and .andThen(), filtering employee records with one card marked INVALID_RECORD." src="https://cdn-images-1.medium.com/max/1024/1*lR0sFpQquPwf6JqpGhnu5w.png" /><figcaption><em>A lambda is a behavior with exactly one place to go. Composition is how small behaviors become one decision.</em></figcaption></figure><p>Before we begin, here’s the source code for today’s lesson:</p><pre>import java.util.function.Function;<br>import java.util.function.Predicate;<br><br>class Employee {<br>    private String name;<br>    private double salary;<br><br>    public Employee(String name, double salary) {<br>        this.name = name;<br>        this.salary = salary;<br>    }<br><br>    public String getName() { return name; }<br>    public void setName(String name) { this.name = name; }<br><br>    public double getSalary() { return salary; }<br>    public void setSalary(double salary) { this.salary = salary; }<br><br>    public boolean isNameNonBlank() {<br>        return name != null &amp;&amp; !name.trim().isEmpty();<br>    }<br><br>    // Static method used for method reference<br>    public static boolean isSalaryAboveThreshold(Employee employee) {<br>        return employee.getSalary() &gt; 60000.0;<br>    }<br>}<br><br>class FormatException extends Exception {<br>    public FormatException(String message) {<br>        super(message);<br>    }<br>}<br><br>@FunctionalInterface<br>interface EmployeeFormatter {<br>    String format(Employee employee) throws FormatException;<br><br>    default String formatOrDefault(Employee employee, String fallback) {<br>        try {<br>            return format(employee);<br>        } catch (FormatException e) {<br>            return fallback;<br>        }<br>    }<br>}<br><br>public class java_13Lambdas {<br>    public static void main(String[] args) {<br>        String[] usernames = new String[] {<br>            &quot;aayush99&quot;, &quot;bob&quot;,<br>            &quot;x&quot;, &quot;claude_code&quot;,<br>            &quot;jd&quot;, &quot;ninacodes&quot;<br>        };<br><br>        Predicate&lt;String&gt; correctUsername = str -&gt; str.length() &gt;= 5;<br>        for (int i = 0; i &lt; usernames.length; i++) {<br>            if (correctUsername.test(usernames[i])) System.out.println(usernames[i]);<br>        }<br>        System.out.println();<br><br>        Employee[] employees = new Employee[] {<br>            new Employee(&quot;Alice Kim&quot;, 72000.0),<br>            new Employee(&quot;bob jones&quot;, 45000.0),<br>            new Employee(&quot;CHARLIE NG&quot;, 138000.0),<br>            new Employee(&quot;dana park&quot;, 61000.0),<br>            new Employee(&quot;Evan Osei&quot;, 29500.0),<br>            // new Employee(null, 61000.0), // Uncommenting this record will trigger exception<br>        };<br><br>        // Arbitrary-object-instance method reference form (Class::instanceMethod)<br>        Predicate&lt;Employee&gt; nameCheck = Employee::isNameNonBlank; // Used here<br>        Predicate&lt;Employee&gt; salaryCheck = Employee::isSalaryAboveThreshold; // Static method reference form<br>        <br>        // Composed Predicate for Bonus Eligibility<br>        Predicate&lt;Employee&gt; bonusEligible = salaryCheck.and(nameCheck);<br><br>        EmployeeFormatter reportLineFormatter = emp -&gt; {<br>            if (emp.getName() == null) throw new FormatException(&quot;Employee name cannot be null&quot;);<br><br>            // Arbitrary-object-instance method reference forms used here<br>            Function&lt;String, String&gt; normalize = String::trim;<br>            Function&lt;String, String&gt; uppercase = normalize.andThen(String::toUpperCase);<br>            <br>            String formattedName = uppercase.apply(emp.getName());<br>            return String.format(&quot;%s - %.1f&quot;, formattedName, emp.getSalary());<br>        };<br><br>        System.out.println(&quot;=== Bonus Eligible ===&quot;);<br>        for (int i = 0; i &lt; employees.length; i++) {<br>            Employee employee = employees[i];<br>            if (bonusEligible.test(employee)) System.out.println(reportLineFormatter.formatOrDefault(employee, &quot;INVALID_RECORD&quot;));<br>        }<br>        System.out.println();<br><br>        System.out.println(&quot;=== All Records ===&quot;);<br>        for (int i = 0; i &lt; employees.length; i++) {<br>            System.out.println(reportLineFormatter.formatOrDefault(employees[i], &quot;INVALID_RECORD&quot;));<br>        }<br>    }<br>}</pre><p>Last time, generics let the compiler know exactly what type a class or method was working with. Today, that same compiler learns to treat <em>behavior</em> as a value. <strong>Lambda expressions</strong> let you write a small piece of logic, like “is this username at least 5 characters long?”, and hand it around like any other object: store it in a variable, pass it to a method, combine it with another one.</p><p>The interesting part isn’t the arrow syntax. It’s the three questions hiding behind it. <em>Where does a lambda’s type come from?</em> <em>What happens when the built-in interfaces can’t describe what your lambda needs to do?</em> And <em>how do small pieces of behavior combine without knowing about each other?</em></p><p>This is <strong>Day 13</strong> of my <strong>Arbiter Learning Journey</strong>, where I’m learning Java from the ground up before eventually building <strong>Arbiter</strong>, a complete microservices-based test management platform.</p><p>In <a href="https://medium.com/dev-genius/learning-java-from-scratch-with-arbiter-generics-bounded-type-parameters-wildcards-and-pecs-c6ff1a636cd8"><strong><em>Day 12</em></strong></a>, I covered bounded type parameters, ? extends and ? super wildcards, and why a raw type compiles with only a note and crashes later. If you want the theory behind it (bounded quantification, use-site variance, and erasure), the <a href="https://medium.com/codex/understanding-java-generics-the-theory-behind-bounded-quantification-use-site-variance-and-the-e8a14870ff6c">Day 12 Guide Theory article</a> covers it.</p><h3>Today’s Topics</h3><ul><li><strong>Lambda expressions</strong>: str -&gt; str.length() &gt;= 5 as a value you can store and pass</li><li><strong>Functional interfaces</strong>: why a lambda needs a target with exactly one abstract method</li><li><strong>Built-in functional interfaces</strong>: Predicate and Function from java.util.function</li><li><strong>Custom functional interfaces</strong>: @FunctionalInterface, and when the built-ins aren&#39;t enough</li><li><strong>Method references</strong>: Employee::isSalaryAboveThreshold, Employee::isNameNonBlank, String::trim</li><li><strong>Composition</strong>: Predicate.and(...) and Function.andThen(...)</li></ul><h3>A Lambda Needs Exactly One Abstract Method to Land On</h3><p>The lesson opens with the smallest possible lambda:</p><pre>Predicate&lt;String&gt; correctUsername = str -&gt; str.length() &gt;= 5;<br>for (int i = 0; i &lt; usernames.length; i++) {<br>    if (correctUsername.test(usernames[i])) System.out.println(usernames[i]);<br>}</pre><p>Nothing on the right side of = says what str is. The compiler works it out from the left side. Predicate&lt;String&gt; is an interface with one abstract method, boolean test(String t). So the lambda&#39;s single parameter must be a String, and its body must produce a boolean. str.length() &gt;= 5 does. The lambda <em>becomes</em> the implementation of test.</p><p>Output:</p><pre>aayush99<br>claude_code<br>ninacodes</pre><p>bob, x, and jd are all shorter than 5 characters, so test returns false for them.</p><p>That “one abstract method” rule isn’t a style guideline. It’s what makes the whole thing work. An interface like that is called a <strong>functional interface</strong>. If it had two abstract methods, the compiler would have no way to know which one the lambda’s body is supposed to implement. I checked what actually happens with an interface that has two:</p><pre>interface Fmt { String format(String s); String other(String s); }<br><br>Fmt f = s -&gt; s;</pre><pre>error: incompatible types: Fmt is not a functional interface<br>    multiple non-overriding abstract methods found in interface Fmt</pre><p>Notice <em>where</em> that error shows up: at the line that writes the lambda, not at the interface. That’s where @FunctionalInterface comes in, and we&#39;ll see it next.</p><h3>A Custom Functional Interface Earns Its Place</h3><p>Java ships plenty of ready-made functional interfaces. The four most common are Function&lt;T, R&gt; (take a T, return an R), Predicate&lt;T&gt; (take a T, return a boolean), Consumer&lt;T&gt; (take a T, return nothing), and Supplier&lt;T&gt; (take nothing, return a T). So why does the lesson define its own?</p><pre>@FunctionalInterface<br>interface EmployeeFormatter {<br>    String format(Employee employee) throws FormatException;<br><br>    default String formatOrDefault(Employee employee, String fallback) {<br>        try {<br>            return format(employee);<br>        } catch (FormatException e) {<br>            return fallback;<br>        }<br>    }<br>}</pre><p>The key is throws FormatException. FormatException extends Exception, so it&#39;s a <strong>checked</strong> exception, the kind Day 11 showed the compiler insisting you handle. None of the built-in interfaces declare a throws clause on their abstract method. Function.apply can&#39;t throw a checked exception, so a lambda that wants to throw one has nowhere to put it. I tried it:</p><pre>Function&lt;String, String&gt; f = s -&gt; {<br>    if (s == null) throw new FormatException(&quot;x&quot;);<br>    return s;<br>};</pre><pre>error: unreported exception FormatException; must be caught or declared to be thrown</pre><p>So the custom interface isn’t decoration. It’s the only way to say “formatting can fail, and the caller has to deal with that.”</p><p>Two more details matter here:</p><ul><li>formatOrDefault is a default method. <strong>Default and static methods don&#39;t count</strong> toward the one-abstract-method limit, so EmployeeFormatter can carry a helper method and still accept a lambda.</li><li>@FunctionalInterface asks the compiler to enforce the rule at the declaration. When I put that annotation on the two-method Fmt from earlier, the error moved from the lambda line to the interface itself:</li></ul><pre>error: Unexpected @FunctionalInterface annotation<br>  Fmt is not a functional interface<br>    multiple non-overriding abstract methods found in interface Fmt</pre><p>Same idea as yesterday’s bound: tell the compiler your intent once, at the declaration, and it catches mistakes there instead of at some distant call site.</p><p>The lambda that implements it is the longest one in the file:</p><pre>EmployeeFormatter reportLineFormatter = emp -&gt; {<br>    if (emp.getName() == null) throw new FormatException(&quot;Employee name cannot be null&quot;);<br><br>    Function&lt;String, String&gt; normalize = String::trim;<br>    Function&lt;String, String&gt; uppercase = normalize.andThen(String::toUpperCase);<br>    <br>    String formattedName = uppercase.apply(emp.getName());<br>    return String.format(&quot;%s - %.1f&quot;, formattedName, emp.getSalary());<br>};</pre><p>A lambda body can be a full block with braces, statements, and a return. Here it can also throw FormatException, because the abstract method it implements declares it.</p><h3>Method References: Whose Method Is Being Called?</h3><p>Some lambdas do nothing except call one existing method. For those, Java has a shorter form: the <strong>method reference</strong>, written with ::.</p><pre>Predicate&lt;Employee&gt; nameCheck = Employee::isNameNonBlank;<br>Predicate&lt;Employee&gt; salaryCheck = Employee::isSalaryAboveThreshold;</pre><p>They look identical, but they’re two different forms. The difference is <em>whose</em> method gets called.</p><p><strong>Static method reference.</strong> isSalaryAboveThreshold is declared static and takes an Employee as its argument:</p><pre>public static boolean isSalaryAboveThreshold(Employee employee) {<br>    return employee.getSalary() &gt; 60000.0;<br>}</pre><p>So Employee::isSalaryAboveThreshold is shorthand for e -&gt; Employee.isSalaryAboveThreshold(e). The Employee passed into test is handed straight to the static method as its argument.</p><p><strong>Instance method of an arbitrary object.</strong> isNameNonBlank is an ordinary instance method that takes no arguments:</p><pre>public boolean isNameNonBlank() {<br>    return name != null &amp;&amp; !name.trim().isEmpty();<br>}</pre><p>There’s no particular Employee object sitting in scope when Employee::isNameNonBlank is written. Instead, the Employee passed into test <em>becomes the object the method is called on</em>: e -&gt; e.isNameNonBlank(). The same form shows up inside the formatter, String::trim means s -&gt; s.trim(), and String::toUpperCase means s -&gt; s.toUpperCase().</p><p>The tell is simple: if the name before :: is a <strong>type</strong> and the method is an <strong>instance</strong> method, the lambda&#39;s parameter becomes the receiver. If it&#39;s a <strong>static</strong> method, the parameter becomes an argument.</p><p>There’s also a third form the lesson doesn’t use: a <strong>bound</strong> instance reference, where a specific object already exists before the ::, like System.out::println (meaning x -&gt; System.out.println(x), always on that one System.out object).</p><figure><img alt="A three-column infographic comparing static, arbitrary-instance, and bound-instance method references, each shown next to its equivalent lambda." src="https://cdn-images-1.medium.com/max/1024/1*9YspFsd-hPtwr-VQBVdBlg.png" /><figcaption><em>The </em><em>:: is the same in every form. What changes is where the object comes from.</em></figcaption></figure><h3>Composition: Small Behaviors, One Decision</h3><p>Here’s where storing behavior in variables pays off. salaryCheck and nameCheck each answer one question. Combining them takes one line:</p><pre>Predicate&lt;Employee&gt; bonusEligible = salaryCheck.and(nameCheck);</pre><p>.and(...) returns a brand-new Predicate that&#39;s true only when both are true. Like &amp;&amp;, it short-circuits: for bob jones, the salary check fails, so the name check never runs. Neither original predicate knows the other exists, and both stay usable on their own.</p><p>Function has its own version. Inside the formatter:</p><pre>Function&lt;String, String&gt; normalize = String::trim;<br>Function&lt;String, String&gt; uppercase = normalize.andThen(String::toUpperCase);</pre><p>.andThen(...) runs normalize first, then feeds its result into String::toUpperCase. So uppercase.apply(&quot;dana park&quot;) trims, then upper-cases, producing DANA PARK.</p><p>Both compositions are doing real work in the program. bonusEligible decides which records print under &quot;Bonus Eligible&quot;:</p><pre>System.out.println(&quot;=== Bonus Eligible ===&quot;);<br>for (int i = 0; i &lt; employees.length; i++) {<br>    Employee employee = employees[i];<br>    if (bonusEligible.test(employee)) System.out.println(reportLineFormatter.formatOrDefault(employee, &quot;INVALID_RECORD&quot;));<br>}</pre><p>Tracing the data by hand: salary above 60000 <em>and</em> a non-blank name gives Alice Kim (72000), CHARLIE NG (138000), and dana park (61000). Bob (45000) and Evan (29500) fall below the threshold. The program agrees:</p><pre>=== Bonus Eligible ===<br>ALICE KIM - 72000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0</pre><p>And uppercase is why every name comes out in capitals regardless of how it was entered: &quot;bob jones&quot;, &quot;CHARLIE NG&quot;, and &quot;Alice Kim&quot; all end up looking the same.</p><h3>Running It</h3><p>Here’s the complete output of the program, exactly as it ran:</p><pre>aayush99<br>claude_code<br>ninacodes<br><br>=== Bonus Eligible ===<br>ALICE KIM - 72000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0<br><br>=== All Records ===<br>ALICE KIM - 72000.0<br>BOB JONES - 45000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0<br>EVAN OSEI - 29500.0</pre><p>Reading it in groups:</p><ul><li><strong>Lines 1–3</strong>: one Predicate&lt;String&gt; lambda filtering usernames by length</li><li><strong>Lines 5–8</strong>: a composed Predicate&lt;Employee&gt; (static + arbitrary-instance method references joined with .and()) picking bonus-eligible employees</li><li><strong>Lines 10–15</strong>: the custom EmployeeFormatter lambda formatting every record, with names normalized through .andThen()</li></ul><h3>The Quiet Trap: A Fallback That Throws Away the Reason</h3><p>The employee array has one line commented out:</p><pre>// new Employee(null, 61000.0), // Uncommenting this record will trigger exception</pre><p>When I uncommented it, nothing crashed. The program ran to completion, and the output changed in exactly one place:</p><pre>=== Bonus Eligible ===<br>ALICE KIM - 72000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0<br><br>=== All Records ===<br>ALICE KIM - 72000.0<br>BOB JONES - 45000.0<br>CHARLIE NG - 138000.0<br>DANA PARK - 61000.0<br>EVAN OSEI - 29500.0<br>INVALID_RECORD</pre><p>That’s formatOrDefault doing its job. The lambda threw FormatException, the default method caught it, and returned &quot;INVALID_RECORD&quot;. It also proves the error path is real and reachable, not dead code.</p><p>But look at what’s missing. The exception carried a message, &quot;Employee name cannot be null&quot;, and it appears nowhere. The catch block never reads e. So the report says <em>a</em> record is invalid, but not which one or why. And in the &quot;Bonus Eligible&quot; section, the null-name employee simply isn&#39;t there: their 61000 salary passes salaryCheck, nameCheck correctly rejects them, and they vanish without a trace.</p><p>Every piece is behaving correctly. That’s what makes it quiet. A fallback is a decision to keep going, and keeping going is often right for a report. But a fallback that discards the exception’s message turns “here’s exactly what went wrong” into “something went wrong somewhere.” It’s the same family as Day 12’s raw type: nothing stops you, and the information you’d need to debug it is the thing that got lost.</p><h3>Key Takeaway</h3><p>Lambdas in Java are behavior with a type, and the type comes from the interface they’re assigned to:</p><ul><li>A lambda needs a <strong>functional interface</strong>, exactly one abstract method, as its target. default and static methods don&#39;t count</li><li>@FunctionalInterface moves the &quot;not a functional interface&quot; error from the lambda site to the declaration</li><li>The built-in interfaces <strong>can’t throw checked exceptions</strong>. When your behavior can fail in a checked way, a custom functional interface is the right tool, not a stylistic choice</li><li>In a <strong>method reference</strong>, a static method takes the parameter as an argument; an instance method on a type takes the parameter as its receiver</li><li>.and() and .andThen() build one behavior from smaller ones that stay independently reusable</li></ul><p>And like the last few lessons, the dangerous case is the one that runs without complaint. A fallback that swallows an exception keeps the program alive but throws away the reason it needed to.</p><h3>The Arbiter Journey Continues</h3><p>One concept at a time, this series is building toward <strong>Arbiter</strong>, a microservices-based test management platform using Java, Spring Boot, databases, testing frameworks, Docker, and AWS. Lambdas will be everywhere in it. Filtering test runs by status, environment, or date is a natural fit for small Predicates combined with .and() and .or(). Turning entities into API response objects is a Function. Spring itself leans on functional interfaces for configuration and callbacks, and the Streams API, which is where this series goes next, is built entirely out of the pieces from today. And the quiet trap matters directly: a test management platform that reports &quot;INVALID&quot; without saying why is exactly the kind of tool nobody trusts.</p><h3>Watch the concept in detail</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FTGlpMeeKM08%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DTGlpMeeKM08&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FTGlpMeeKM08%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c0f9aeaf37f99e341cc1fe27369bdff/href">https://medium.com/media/3c0f9aeaf37f99e341cc1fe27369bdff/href</a></iframe><h3>Follow along with the complete Arbiter playlist</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DPLZG2gr4IjsZM&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fplaylist%3Flist%3DPLZG2gr4IjsZM%26si%3DStFLpldNdIOjc3ub&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FrCDQ-YLfLIY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="853" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href">https://medium.com/media/04268183d86e8630d9e771d901b3aa12/href</a></iframe><h3>GitHub Repository</h3><p>You can follow the complete learning journey, source code, explanations, and future lessons here:</p><p><a href="https://github.com/Someone-anon-coder/Arbiter">GitHub - Someone-anon-coder/Arbiter: Welcome to Arbiter - a documented, hands-on journey from Java fundamentals to a deployed microservices platform.</a></p><h3>Follow the Complete Arbiter Series</h3><p>Every Guide and Guide Theory article in this series, collected in one place:</p><ul><li><a href="https://computer-info-1.medium.com/list/arbiter-java-20daa21849c8">List: Arbiter (Java) | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/arbiter-java-theory-13ecf0e21149">List: Arbiter (Java) Theory | Curated by Someone | Medium</a></li></ul><h3>Explore My Other Learning Series</h3><p>If you’re interested in my other programming and technology learning journeys, you may also enjoy:</p><ul><li><a href="https://computer-info-1.medium.com/list/cybersecurity-cba13dd0be16">List: Cybersecurity | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-8e0b3bcab940">List: Python Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/python-guide-theory-81528784ebfe">List: Python Guide - Theory | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-e70e25ca8b42">List: GoLang Guide | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/golang-guide-theoretical-082d8e7624ab">List: GoLang Guide Theoretical | Curated by Someone | Medium</a></li><li><a href="https://computer-info-1.medium.com/list/my-progress-matplotlib-669052e8da38">List: My Progress - Matplotlib | Curated by Someone | Medium</a></li></ul><h3>What’s Next?</h3><p>In <strong>Day 14</strong>, we move into the <strong>Streams API</strong>: map, filter, reduce, and collect. Every one of those operations takes a functional interface as its argument, so today&#39;s Predicates, Functions, and method references become the building blocks of whole data pipelines, without a single index-based for loop.</p><p>Happy coding! ☕</p><p><strong>Companion theory article:</strong> For the theoretical background behind this lesson, see the <a href="https://computer-info-1.medium.com/understanding-java-lambdas-the-theory-behind-lambda-calculus-functional-interfaces-and-method-6cce70a4beb9?sharedUserId=computer-info-1"><strong><em>Day 13 Guide Theory</em></strong></a> piece, published alongside this one. For the previous lesson’s theory, see <a href="https://medium.com/codex/understanding-java-generics-the-theory-behind-bounded-quantification-use-site-variance-and-the-e8a14870ff6c">Day 12 Guide Theory</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dcf5bd07ad9" width="1" height="1" alt=""><hr><p><a href="https://blog.devgenius.io/learning-java-from-scratch-with-arbiter-lambdas-functional-interfaces-method-references-and-1dcf5bd07ad9">Learning Java from Scratch with Arbiter — Lambdas: Functional Interfaces, Method References, and…</a> was originally published in <a href="https://blog.devgenius.io">Dev Genius</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>