#source-reading
Posts
-
Digging into Gradle's Source 1: How I Read It with AI, and the Map
The first post of a series on reading Gradle's source, starting with diagrams and a 3-minute hands-on run that show what Gradle is, how it works and what it is for. It covers which version is read (master, a 9.10.0 development build), how to get the same commit and build Gradle with Gradle inside Docker, a platform map of the roughly one-million-line codebase, the main road one gradle command travels through the client and the daemon to your task action, and how AI-written notes are re-checked against the source.
-
Digging into Gradle's Source 2: The Big Picture — Client and Daemon, Service Scopes, and the Three Build Phases
A map of the Gradle source for intermediate developers opening it for the first time. We check against the real source and examples we ran ourselves which four processes take part in one gradle command, how the daemon's state is layered (process, session, build tree, build, project) and matched one-to-one with service scopes, which class and which state the initialization, configuration and execution phases really are, and how the model (Gradle, Settings, Project, Task) and the execution side (execution plan, nodes, execution engine) fit together — then lay out which part of this map each of parts 3 to 12 reads.
-
Digging into Gradle's Source 3: From the gradle Command to the Daemon — the Launcher, the Wrapper and the Daemon Protocol
When you type gradle in a terminal, the build is not done by that process but by a daemon JVM hiding behind it. This post follows the 64 MiB client the shell script starts, the three paths that decide whether a daemon is used and what --no-daemon really means, how registry.bin is used to find a daemon and judge compatibility, the order in which a daemon is started and connected to, the 16-step command chain and the Idle/Busy states inside the daemon, the conditions under which a daemon stops itself, and how gradlew and IDEs reach the same daemon — all checked against the source at a fixed commit and with Docker examples.
-
Digging into Gradle's Source 4: The Service Registry — the DI Container Inside Gradle
Who puts a FileSystemOperations into your task when you mark its constructor with @Inject? This post reads DefaultServiceRegistry, one per scope, which looks services up through its parents; the rules for declaring services with @Provides methods and decorators; lazy singletons created on first lookup; ServiceScopeValidator, which keeps services in their proper scope; GradleModuleServices, through which 57 modules add services to every scope; and the _Decorated classes that fill in abstract getters. We use the registry directly outside a build and print the stack of a task being created to check it all against the source.
-
Digging into Gradle's Source 5: The Build Lifecycle — Where Initialization, Configuration and Execution Split
We follow, in the source of the reference commit, how a build request received by the daemon travels down a chain of executors to the build lifecycle, how StateTransitionController guards state transitions, how initialization reads init scripts and settings, how the configuration phase configures projects, where the configuration time barrier and the task graph sit, and how the build wraps up with buildFinished and the Flow API. We hook every lifecycle callback to print their order, read the build operation tree, and fail the build on purpose in each of the three phases.
-
Digging into Gradle's Source 6: Projects and Build Scripts — How build.gradle.kts Gets Run
What does one build.gradle.kts file turn into inside Gradle? We follow the code that builds DefaultProject from the descriptors settings left behind, the evaluator chain that runs under a project lock with its beforeEvaluate and afterEvaluate states, the selector that picks Groovy or Kotlin by the end of the file name, the two DSLs that cut out plugins {} and compile twice, the generated accessors that make tasks.compileJava possible, and the class loader scopes that run from settings down to subprojects, running the same script in both DSLs and printing a stack trace to check each step against the source.
-
Digging into Gradle's Source 7: Applying Plugins — From the plugins {} Block to apply()
What does a single plugins { java } line turn into in the source? DefaultPluginRequestApplicator resolves every request first and only then applies them all; a resolver chain asks core plugins, buildSrc, included builds and the Plugin Portal in turn, using marker artifacts; META-INF/gradle-plugins descriptors turn an id into a class; DefaultPluginManager runs a plugin only once even if you apply it twice; withPlugin fires at a precise moment; extensions get attached; java pulls in java-base and base; and buildSrc and precompiled script plugins fit in — all checked with a plugin we write ourselves.
-
Digging into Gradle's Source 8: Tasks and Lazy Configuration — DefaultTask, TaskContainer and Provider
A single tasks.register line does not create a task. We follow when the TaskCreatingProvider that only holds a name and a configuration block becomes a real task, how configureEach reaches tasks that exist now and tasks that come later, which APIs like withType { } quietly create every task, how TaskFactory and DefaultTask hold the action list, why a Provider that carries "the task that makes this value" gives you ordering without dependsOn, and the three ways a Property gets locked — checked against the source with three runnable examples.
-
Digging into Gradle's Source 9: The Task Graph and the Execution Plan — What, in What Order, How Many at Once
How does the pA in gradle pA become packageApp? What do dependsOn, mustRunAfter, shouldRunAfter and finalizedBy turn into inside the execution plan, and who decides the order? Who picks the next node while the build runs, what exactly do --parallel and --max-workers limit, and how does the Worker API split up the work inside a single task? We answer each question from the source at a fixed commit and from examples we actually ran (whenReady, --dry-run, thread names, jstack).
-
Digging into Gradle's Source 10: The Execution Engine and Incremental Builds — How UP-TO-DATE Is Decided
How does the single word UP-TO-DATE on your console get decided in the source? We follow DefaultTaskNodeExecutor, which filters tasks before the engine, TaskExecution, which wraps a task as a unit of work, the execution engine built from twenty-five nested steps, ResolveChangesStep and SkipUpToDateStep, which compare the last execution record with the current state, the file fingerprints that @PathSensitive changes, the VFS that remembers file state between builds, and InputChanges, which hands a task only the files that changed — all checked against the source with examples we actually ran.
-
Digging into Gradle's Source 11: The Build Cache and the Configuration Cache — Two Ways to Reuse Work
Where in the source do FROM-CACHE and "Reusing configuration cache." come from? This post covers ResolveMutableCachingStateStep and BuildCacheStep in the execution engine's step chain, what goes into the MD5 cache key, @CacheableTask and the list of reasons not to cache, the local directory cache where the key is the file name and each entry is a tar.gz, the configuration cache that serializes the whole work graph and reloads it even on the very first build, the fingerprint that catches environment variable reads by rewriting bytecode, and the problem report — all checked against the source at a fixed commit and with Docker examples.
-
Digging into Gradle's Source 12: Dependency Resolution — From One implementation Line to Jar Files, and Wrapping Up the Series
What does the single implementation line in build.gradle.kts become in the source? This post follows the DependencyHandler.add() call behind the generated accessor, configurations whose roles are split into declare, resolve and consume, resolution that waits until someone asks for the file list, the loop in which DependencyGraphBuilder grows the graph, the LatestModuleConflictResolver that picks the higher of two versions of the same library and the places where strictly and force change that result, and the two-pass lookup that checks the cache before the repositories, together with --offline and the read-only cache — all checked against the source at a fixed commit and with Docker examples. It then wraps up the 12-part series.
-
Digging into Kafka's Source 1: How I Read It with AI, and the Map
The first post of a series that reads the Kafka source — it starts by showing what Kafka is, how it works and what it is for, with diagrams and a 3-minute hands-on run. It covers which version is read (trunk, 4.5.0-SNAPSHOT) and how it relates to the stable 4.4.0, how to get the same source and build it with Gradle in Docker, the processMessages step that generates code from JSON schemas, a module map of the roughly 720,000-line main codebase, the big road one record travels from send() through the disk and replication to poll(), and how AI-written notes are re-checked against the source.
-
Digging into Kafka's Source 2: The Big Picture — Clients, Brokers, Controllers and Partitions
A big-picture map for intermediate developers opening the Kafka source for the first time. Starting from the main method of kafka.Kafka that kafka-server-start.sh calls, we follow how process.roles decides whether BrokerServer, ControllerServer and SharedServer get built, what a broker creates when it starts, how the controller decides topics, partitions and leaders through the metadata log and spreads them to the brokers, how a client finds a leader through a Metadata response, and which way the modules depend on each other — all checked against the real source and examples I actually ran — and we mark which part of the map Parts 3–12 zoom into.
-
Digging into Kafka's Source 3: The Wire Protocol — Requests and Responses Generated from JSON Schemas
The bytes a producer and a broker exchange over TCP always have one shape, [4-byte length][request header][body]. We follow in the source how 203 JSON schemas that define that shape become 391 Java classes at build time, how the ApiVersions exchange picks a version, and how flexible versions and tagged fields let old and new versions live together, then check the bytes ourselves with an example that sends requests over a raw socket using nothing but the generated classes.
-
Digging into Kafka's Source 4: The Broker's Network Layer — Acceptors, Processors and Request Handlers
We follow, in SocketServer.scala, how the three kinds of threads in a broker's jstack — socket-acceptor, network-thread and request-handler — share the work of a single request. One Acceptor per listener; a Processor reads the bytes, puts the request on the request queue and mutes the connection; a request handler branches on KafkaApis.handle()'s match; and the response goes back to the Processor that read the request. With jstack, a raw-socket client and the request log we check how mute/unmute keeps one connection's requests in order by handling them one at a time, and where num.network.threads, num.io.threads and queued.max.requests take effect, and we compare it all with Tomcat's NIO connector.
-
Digging into Kafka's Source 5: Inside the Producer — From send() to Batches and the Sender Thread
KafkaProducer.send() sends nothing over the network. It serializes the record, picks a partition, appends it to a per-partition batch in the RecordAccumulator and hands back a Future; the one thread that actually sends is kafka-producer-network-thread. We follow in the source the if in ready() where linger.ms and batch.size apply, the built-in partitioner that sticks keyless records to one partition, the BufferPool that hands out batch memory and max.block.ms, retries and delivery.timeout.ms, and max.in.flight.requests.per.connection, then check them with examples that count batches with kafka-dump-log.sh and print which thread runs the callback.
-
Digging into Kafka's Source 6: How the Broker Accepts Records — The Record Batch Format and the Produce Path
A record batch sent by the producer is a 61-byte header followed by records squeezed with varints. We read those bytes straight out of a segment file, then follow one Produce request through KafkaApis → ReplicaManager → Partition → UnifiedLog → LogValidator → LogSegment with the source and the broker's TRACE log. Using hand-built Produce requests we confirm that the broker writes the batch almost as-is, fixing only a few header fields (the CRC stays the same), when it recompresses because the topic's compression.type differs, and where acks=0/1/all change the moment the response goes out.
-
Digging into Kafka's Source 7: Log Storage — Segments, Indexes, Retention and Compaction
When does a file like 00000000000000003830.log appear in a partition directory, how is one offset turned into a byte position in that file, who deletes a segment once retention.ms has passed, and how does cleanup.policy=compact sweep away old values of the same key? We follow UnifiedLog, LocalLog, LogSegment, OffsetIndex and LogCleaner in the source, and check with examples the five roll conditions, the sparse index that writes one entry per 4 KB and lives in mmap, the two-step delete through a .deleted rename, the SkimpyOffsetMap built from MD5 hashes of keys, and the recovery point used when a broker comes back from a crash.
-
Digging into Kafka's Source 8: The Read Path — Fetch Requests, Long Polling and Zero-Copy
We follow a consumer's Fetch request through the Kafka 4.5.0-SNAPSHOT broker — KafkaApis.handleFetchRequest() → ReplicaManager.fetchMessages() → UnifiedLog.read() → LogSegment.read() — until it becomes a slice of a file (FileRecords), how it waits in the purgatory (DelayedFetch) for up to fetch.max.wait.ms when there is not enough data, and how FileChannel.transferTo() finally sends it to the socket without passing through the JVM. Request logs, JMX and JFR examples show incremental fetch sessions, why consumers and followers share one Fetch, and why zero-copy disappears under TLS.
-
Digging into Kafka's Source 9: Replication — The ISR, the High Watermark and the Wait Behind acks=all
A follower broker's ReplicaFetcherThread sends Fetch requests to the leader and copies its log, and the leader learns each follower's position from the offset in those Fetch requests and raises the high watermark. We follow, in Partition.scala and AbstractFetcherThread.scala, the rule for dropping out of the ISR (replica.lag.time.max.ms and lastCaughtUpTimeMs), the AlterPartition request that tells the controller about ISR changes, the condition an acks=all request waits for as a DelayedProduce in the purgatory, the places where min.insync.replicas rejects a request, and how a follower truncates a diverged log with leader epochs after the leader changes — and we check it all with examples that freeze and kill brokers in a three-broker cluster.
-
Digging into Kafka's Source 10: KRaft and Metadata — How the Controller Remembers the Cluster
The cluster's ledger — topics, partitions, leaders, the list of brokers — is stored as records in __cluster_metadata, an ordinary Kafka log, and the controllers keep that log together with Raft. We follow it through the source: the roles in KafkaRaftClient (Prospective, Candidate, Leader, Follower) with Vote and Fetch, a high watermark set by a majority of voters, a QuorumController that runs on a single event thread and applies records to memory before they commit (and rolls them back with SnapshotRegistry), the MetadataLoader that turns records into MetadataDelta and MetadataImage on the broker, and heartbeats and fencing — checked with kafka-dump-log, kafka-metadata-quorum and a three-controller election.
-
Digging into Kafka's Source 11: The Consumer and the Group Coordinator — Inside poll() and Rebalancing
KafkaConsumer is a thin shell that hands every call to either ClassicKafkaConsumer or AsyncKafkaConsumer, depending on group.protocol. With the default, classic, the thread that calls poll() runs the rebalance itself through JoinGroup and SyncGroup, and a leader consumer computes the assignment. With the consumer protocol (KIP-848), a background thread talks to the broker with a single ConsumerGroupHeartbeat request and the broker decides the assignment. We follow both, then the broker-side group coordinator and its CoordinatorRuntime, which writes group state to __consumer_offsets as records and rebuilds it by reading them back — checked with an example that adds two consumers one after the other, and with kafka-dump-log.sh.
-
Digging into Kafka's Source 12: Idempotence and Transactions — How Exactly-Once Is Built, and Wrapping Up the Series
We follow in the source what happens behind enable.idempotence=true and a transactional.id. Where the broker uses the producer id, epoch and sequence to spot duplicates from retries; how the transaction coordinator records state in __transaction_state and has commit/abort markers (control records) written to every partition; how a read_committed consumer filters with the last stable offset (LSO) and a list of aborted transactions; and how an old producer with the same transactional.id is fenced off by its epoch — checked with raw-socket, kafka-dump-log and kafka-transactions examples. As the last post, it also looks back over parts 1–12 to close the series.
-
Digging into MariaDB's Source 1: How I Read It with AI, and the Map
The first post of a series on reading the MariaDB server's source, starting with pictures and a 3-minute hands-on run that show what MariaDB is, how it works and what it is for. It covers which version is read (the main branch, 13.2.0 alpha) and how it differs from the LTS releases, how to get the same source including submodules and make a Debug build in Docker (including why the build broke on Windows because of CRLF), a folder map of the 2.59-million-line codebase, the big road one query travels from the connection thread to InnoDB as confirmed with a gdb backtrace, and how AI-written notes are re-checked against the source.
-
Digging into MariaDB's Source 2: The Big Picture — the Server Layer, Storage Engines, and the Life of One Query
The map for this series, written for intermediate developers opening the MariaDB source for the first time. Why the server layer (sql/) and the storage engines (storage/) are separate, what their boundary looks like — handler (one opened table) and handlerton (one table of function pointers per engine) — the path one SELECT takes from connection → THD → parsing → preparation → optimization → execution → handler → engine and back through Protocol, and MEM_ROOT, which throws all those objects away in one go after every statement. Checked against the real source and gdb backtraces.
-
Digging into MariaDB's Source 3: Startup and Plugins — Until mariadbd Takes Its First Connection
What happens between starting mariadbd and its first connection? We follow mysqld_main() in sql/mysqld.cc — how a line in my.cnf becomes a system variable, how plugin_init() registers and initializes everything as a plugin (even InnoDB and Aria), where ha_initialize_handlerton() lets an engine fill its handlerton, the signal thread and the poll() loop, and how shutdown runs startup in reverse — checked with the error log and gdb.
-
Digging into MariaDB's Source 4: Connections and Threads — One Thread per Connection, and the Thread Pool
We follow the source from the moment mariadbd's main thread accepts a connection with poll() and wraps it in a CONNECT, to the point where it hands it to the scheduler (a bundle of function pointers) chosen by thread_handling. We look at the default one-thread-per-connection mode and its thread cache, the login step that creates the THD and authenticates, and how the thread pool — thread groups, a listener, workers and a timer_thread — detaches idle connections from threads, checking each step with ps, information_schema and gdb, and comparing it with httpd's MPMs and Tomcat's endpoint.
-
Digging into MariaDB's Source 5: The Protocol and Command Dispatch — How One Packet Reaches dispatch_command
How the bytes a client sends become packets, how a single first byte decides between COM_QUERY, COM_PING and COM_STMT_EXECUTE, and how the result turns back into packets on the way out — followed through sql/net_serv.cc, do_command() and dispatch_command() in sql_parse.cc, and protocol.cc. We talk to the server directly over bash's /dev/tcp with no client library, and run the query cache to see why it can look up a result before the SQL is even parsed.
-
Digging into MariaDB's Source 6: The Parser — How an SQL Statement Becomes LEX and Item Trees
We follow one SQL statement from a plain string through the lexer (Lex_input_stream), where it becomes tokens, into the rule actions of the Bison-generated parsers (MYSQLparse and ORAparse), where it becomes LEX, SELECT_LEX, TABLE_LIST and Item trees. We look at the build rule that turns one grammar file into two parsers, the keyword table and state_map, and the place the "near '...' at line N" error comes from, then print the trees the parser leaves behind with a Bison trace and gdb.
-
Digging into MariaDB's Source 7: Preparation — Checking Privileges, Opening Tables, Resolving Names
We follow, in the source, the three things the server does with the parser's LEX before it plans anything. In mysql_execute_command()'s big switch, check_table_access() checks privileges; open_and_lock_tables() takes metadata locks and pulls TABLE_SHARE and TABLE out of their caches; JOIN::prepare() binds column names to Field objects. We watch column-privilege errors 1142 and 1143, Opened_tables and Table_open_cache_hits moving, an ALTER TABLE waiting behind an open transaction with a plain SELECT queued behind it, and the call stack that raises errors 1054 and 1052.
-
Digging into MariaDB's Source 8: The Optimizer — JOIN::optimize and Choosing a Plan
We follow, in the source, what the MariaDB optimizer does before a single line of EXPLAIN appears — the stages of JOIN::optimize() (tidying the conditions, estimating row counts, range analysis, searching join orders), the new cost model where 1 means one millisecond, how a histogram flips a plan, and where EXPLAIN and optimizer_trace come from — checking each with examples and gdb.
-
Digging into MariaDB's Source 9: Execution and the Handler API — How the Nested-Loop Join Calls the Engine
We follow, in the source, the place where the optimizer's plan actually reads rows. From JOIN::exec() into the nested loop of do_select(), sub_select() and evaluate_join_record(), through the read functions chosen for each EXPLAIN type and the join buffer, past the handler's ha_ wrappers, and down to ha_innobase::rnd_next() and write_row(). We count calls with the Handler_read_* status variables, confirm the whole path from do_command to the engine with one gdb backtrace, and see that switching the engine to MEMORY, Aria or BLACKHOLE still goes through the same spots.
-
Digging into MariaDB's Source 10: InnoDB Part 1 — Pages, the Buffer Pool and B-trees
We go below the handler and read, in the source, the three containers InnoDB keeps data in. The 16KB page with its FIL header, page header, records and page directory; the buffer pool that keeps pages in memory (buf_page_get_gen, the old part of the LRU list, dirty pages and the page cleaner); the clustered-index B-tree that is the table itself (search_leaf, page splits); and the mini-transaction mtr_t that bundles page latches with redo. We open an .ibd file with od and read page headers, look inside the buffer pool through information_schema, grow a table to count B-tree height and splits, and use gdb to watch SELECT and INSERT reach the B-tree.
-
Digging into MariaDB's Source 11: InnoDB Part 2 — Transactions, MVCC, Undo and Locks
We follow, in the source of the reference commit, how one COMMIT travels from the server layer's ha_commit_trans() to InnoDB's trx_t::commit(), how REPEATABLE READ shows you old values through a ReadView and the undo chain, how purge cleans up versions nobody can see any more, and how row locks, gap locks, lock waits and deadlock detection work. Two-session examples show the snapshot, a lock wait, a deadlock and the history list length with our own eyes.
-
Digging into MariaDB's Source 12: Two Logs — the Redo Log, the Binlog and Crash Recovery, and Wrapping Up
What it means for data to be on disk when COMMIT returns. We follow InnoDB's redo log (log_sys, LSNs, mtr_t::commit, log_write_up_to) and the spot where innodb_flush_log_at_trx_commit splits, the checkpoint and the layout of ib_logfile0, crash recovery after kill -9 that rolls forward with redo and back with undo, the server-level binlog and its events, and the two-phase and group commit that tie the two logs together by XID. Using DBUG crash points we kill the server in both gaps of the two-phase commit and watch what recovery does, then wrap up all twelve parts of the series.
-
Digging into nginx's Source 1: How I Read It with AI, and the Map
The first post of a series on reading nginx's source, starting with pictures and a 3-minute hands-on run that show what nginx is, how it works and what it is for. It then covers which version we read (the 1.31.9 mainline development version) and how it differs from stable 1.30.x, how to get the same source and build it in Docker with auto/configure and make, a folder map of the 260,000-line source, the main path one request takes from accept to sendfile — checked with gdb and strace — and how to read with AI while verifying its notes against the source.
-
Digging into nginx's Source 2: The Big Picture — Master and Workers, Modules, the Cycle and Config Contexts
The map for the whole series, written for intermediate programmers opening the nginx source for the first time. Where the single master and the workers each spend their time waiting, how every feature plugs in as one ngx_module_t and why module types are the ASCII letters "CORE" and "HTTP", what ngx_cycle_t and conf_ctx hold for a running nginx, how the main, events, http, server and location contexts of nginx.conf are checked with bit flags, and how one worker's event loop juggles hundreds of connections — all checked against the real source with ps and gdb examples.
-
Digging into nginx's Source 3: Memory Pools and Core Data Structures — ngx_pool_t, ngx_str_t, ngx_buf_t and ngx_chain_t
You will hardly find free() in nginx's C code either. Memory that shares a lifetime — one request, one connection — goes into a pool and is thrown away in one go. This post checks, against the real source, debug logs and gdb, how ngx_pool_t chains its blocks and uses the failed counter, the separate large list for big allocations, cleanups, the non-NUL-terminated ngx_str_t, arrays, lists, queues and hashes, and the ngx_buf_t and ngx_chain_t that carry responses.
-
Digging into nginx's Source 4: Startup and Reading the Config — From main() to ngx_init_cycle
When you start nginx, main() reads the command line, and ngx_init_cycle() reads nginx.conf and builds one complete set of configuration called a cycle — only then does the master process start running. We follow how one parser function handles three cases (a file, a block, and the -g command-line string), how ngx_conf_read_token() splits text into words one character at a time, where the error messages come from, and how reload calls the same ngx_init_cycle() again and inherits listening sockets and shared memory from the old cycle — using the nginx 1.31.9 source and gdb and strace examples.
-
Digging into nginx's Source 5: Modules and Directives — How One Line of nginx.conf Becomes a Module's Config Struct
An nginx module is a single ngx_module_t struct, and a directive is one row in the ngx_command_t table that struct points to. We follow the module array configure generates, dynamic modules read with load_module, ngx_conf_handler() finding a function by directive name, ngx_http_block() creating per-module config structs for every http, server and location, and the merge that finishes once, at config time. We build a dynamic module with four directives to watch the callback order, the merged values and the error messages.
-
Digging into nginx's Source 6: Master and Workers — Processes, Signals and Zero-Downtime Reload
We follow src/os/unix/ngx_process_cycle.c to see how one nginx -s reload wakes the master's sigsuspend() loop, starts new workers with the new config, and lets the old workers finish what they were doing before they leave. Why the signal handler only sets a flag, the socketpair channel between master and workers, the respawn, just_spawn and exiting bits in the ngx_processes[] table, how a worker killed with kill -9 comes back, and the USR2 binary upgrade — all checked with strace, debug logs and ps.
-
Digging into nginx's Source 7: The Event Loop — epoll, Timers, Posted Events and accept
We follow one turn of ngx_process_events_and_timers(), the loop that lets a single-threaded worker juggle thousands of connections. How epoll is plugged in through a function-pointer table (ngx_event_actions), the connection array pre-built to worker_connections slots and its free list, EPOLLET and the instance bit that filters stale events, how the red-black timer tree becomes epoll_wait's timeout, the posted queues, and which of accept_mutex (off by default), EPOLLEXCLUSIVE and reuseport decides who accepts a new connection — checked with strace, gdb and the debug log.
-
Digging into nginx's Source 8: Reading an HTTP Request — From a Connection to ngx_http_request_t
We follow, through the source, how one accepted socket becomes an ngx_http_request_t inside nginx — why the request struct is not created until data arrives, the switch-based state machines that parse the request line and headers, where client_header_buffer_size and large_client_header_buffers are used, the order in which the Host header picks a server block, and keep-alive and pipelining, all checked with debug logs, gdb and nc.
-
Digging into nginx's Source 9: The 11 Phases and Finding the location — In What Order a Request Is Handled
Once its headers are read, a request walks through 11 phases inside nginx. We check in the source how the handlers that modules register are flattened at config time into a single ph[] array, and how ngx_http_core_run_phases() drives that array through checker functions. We also see how location blocks become a prefix tree and are chosen in the order =, ^~, regex and 1.31.x's predicate locations, and which slot of the array rewrite last/break, internal redirects and named locations go back to — with debug logs and gdb.
-
Digging into nginx's Source 10: Filters and Static Files — How a Response Flows Out
We follow one static file from the handler to the socket in the nginx 1.31.9 source. The static handler that never reads the file and only records "from this byte to that byte of the file" in an ngx_buf_t, the header and body filter chains glued together with two function pointers and the ngx_modules.c order that decides them, the copy filter and ngx_output_chain that decide whether to read the file into memory, and the write filter that holds data back and then sends it with writev + sendfile — all checked with gdb, strace and the debug log.
-
Digging into nginx's Source 11: Upstream and Proxy — One Request Through the Reverse Proxy
We follow, in the source, how one proxy_pass line installs a content handler on a location, how the proxy module fills the callbacks of ngx_http_upstream_t, and how upstream then takes care of the non-blocking connect, sending the request, reading the response header and streaming the body. We check the request the backend actually receives (HTTP/1.1 by default in this version, and no Connection header), where 502 and 504 are decided, proxy_next_upstream, and proxy_buffering on/off with its temporary file — using debug logs, gdb and a tiny backend.
-
Digging into nginx's Source 12: Load Balancing, Upstream Keepalive and the Stream Module — and Wrapping Up the Series
When an upstream {} block lists several servers, how does nginx pick one for each request? We trace the load-balancing frame built from three function pointers (peer.init, get, free), the smooth weighted round robin that spreads weights 5:1:1 as a a b a c a a, max_fails and fail_timeout, the keepalive module that wraps get/free to park connections in a cache (on by default in this version, even if you never write it), and the stream module that puts the same foundation under plain TCP — checked against the source and Docker examples — and then wrap up the series.
-
Digging into Apache httpd's Source 1: How I Read It with AI, and the Map
The first post of a series on reading the httpd source, starting with pictures and a 3-minute hands-on run that show what Apache httpd is, how it works and what it is for. It covers which version is read (trunk, 2.5.1-dev), how to get the same source and build it in Docker (including the places I actually got stuck), a folder map of the roughly 350,000-line codebase, the big road one request travels, and how AI-written notes are re-checked against the source.
-
Digging into Apache httpd's Source 2: Before Reading a Server Written in C — Structs, Function Pointers, Macros
Where does the saying "C structs are more beautiful than C++ or Java objects" come from? Without classes or interfaces, how does httpd plug modules in? We check it against the real source and real runs — memory layout with nothing hidden, interfaces built from function pointers, a void * array indexed by module_index, hook functions stamped out by macros, the MMN check that lets httpd trust a separately compiled .so — and the things C will not do for you.
-
Digging into Apache httpd's Source 3: The Big Picture — Core, APR, MPM, Modules and the Three Big Structs
A map for anyone opening the Apache httpd source for the first time. We look at the layers (operating system, APR, core, MPM, modules), why even the core is a module, how modules compiled into the binary differ from ones loaded with LoadModule, the three structs server_rec, conn_rec and request_rec, the pool hierarchy, and the path a single request takes — all checked against the real source and examples I actually ran.
-
Digging into Apache httpd's Source 4: APR Memory Pools — Managing Memory Without free()
You will hardly find free() in httpd's C code. Instead of handing memory back piece by piece, httpd groups everything with the same lifetime — "one request", "one connection" — into a pool and throws it away in one go. This post looks at APR pool nodes and the allocator, why apr_palloc is just a pointer bump, the order cleanups run in, where httpd creates and destroys its pools, and the two classic mistakes — all checked against the real source and real runs.
-
Digging into Apache httpd's Source 5: main() and Startup — Why the Config Is Read Twice, and Restarts
What happens inside main() when you start httpd, followed through all 914 lines of server/main.c. Why the config file is read twice, how one apachectl graceful makes main()'s loop go round once more, and where a server started as root drops to User — checked against the source and against real log output.
-
Digging into Apache httpd's Source 6: Modules and Hooks — How a Module Plugs into the Server
Nearly every feature of httpd is a module, and a module plugs into the server by attaching functions to fixed points called hooks. Using the source at a pinned commit and three small modules written for this post, we look at the module struct, the two ways a module gets registered (static modules and LoadModule), what the hook macros really expand to, how RUN_FIRST differs from RUN_ALL, and how the call order is decided from order values and the names of other modules.
-
Digging into Apache httpd's Source 7: The Config File and Directives — How One Line of httpd.conf Becomes a C Struct
How does one line you write in httpd.conf end up calling a module's C function and filling in a struct? We follow server/config.c — reading the file into a tree and then walking it, the hash that finds a directive by name, argument styles like TAKE1, FLAG and ITERATE, the bit test behind "not allowed here" and AllowOverride, and when the values in Directory, Location and VirtualHost sections get merged — all checked with a directive we wrote ourselves.
-
Digging into Apache httpd's Source 8: MPMs — How prefork, worker and event Share Out Connections
Follows in the source what the single line LoadModule mpm_event_module in httpd.conf actually changes. How an MPM can be swapped as one module (the mpm hook and ap_mpm_query), prefork that only uses processes, worker that splits the job between a listener thread and a queue, event that takes keep-alive connections off the threads and hands them to epoll, the parent's once-a-second loop that grows and shrinks the children, the shared-memory scoreboard, and where StartServers, MaxRequestWorkers and ThreadsPerChild end up in the code — all checked with ps, mod_status and gdb.
-
Digging into Apache httpd's Source 9: Connections and Reading a Request — From a Socket to a request_rec
Follows one socket handed over by the MPM as it becomes a conn_rec, and then a request_rec once the request line and headers are read — all in the httpd source. Why the core always stands last in the hooks, where the keep-alive loop lives depending on the MPM, how trunk moved request-line reading into a filter, and the two steps that pick a virtual host from the Host header, each checked with an example.
-
Digging into Apache httpd's Source 10: Request Processing — From a URL to a File and a Handler
How does the single line GET /hello.html become a file on disk and a handler that sends it out? We follow the order of the phases in ap_process_request_internal() through the real source. We also look at the three walks that merge Location, Directory and Files settings, mod_dir's subrequest and ErrorDocument's internal redirect, using a module that logs every phase.
-
Digging into Apache httpd's Source 11: Filters and Bucket Brigades — How a Response Flows Out
What shape a response takes inside httpd on its way from the handler to the socket, followed for someone opening the source for the first time. APR-util's buckets and brigades (a ring), PIPE and FILE buckets that change shape when read, filters lining up in six tiers and ap_pass_brigade, chunked encoding and sendfile, and what the core output filter does when the socket is full — all checked with the log of a filter module written for this post.
-
Digging into Apache httpd's Source 12: Handlers and Static Files — How a File Becomes a Response
How the "handler" set by SetHandler and AddHandler differs from the Content-Type, and how the core's default_handler() answers any request nobody else claimed by sending a single file bucket. We also check ETag and 304, Range and 206, the trailing-slash 301 and DirectoryIndex, and which system calls EnableSendfile and EnableMMAP actually change — with curl and strace.
-
Digging into Apache httpd's Source 13: Authentication and Authorization — How a Require Line Is Decided
We follow what AuthType Basic, AuthUserFile, Require valid-user and <RequireAll> in httpd.conf actually do in the source. Why "who are you" (authentication) and "may you come in" (authorization) live in separate modules, providers that are swapped in by name, the Require tree and its GRANTED, DENIED and NEUTRAL results, and the order in request.c that runs the same decision twice — all checked against the real source and example output.
-
Digging into Apache httpd's Source 14: mod_proxy — Reverse Proxy and Load Balancer
We follow one ProxyPass line through the source until the request reaches another server. We see where the URL turns into a "proxy:..." r->filename, how proxy_handler() hands the work to the scheme handler hook, the worker and connection pool that each backend gets, how mod_proxy_http streams the response back through brigades, and how the byrequests balancer alternates between two backends — checked with two backends running in a container.
-
Digging into Apache httpd's Source 15: mod_ssl — Where TLS Plugs In
The httpd core knows nothing about TLS, yet one SSLEngine on line gives you HTTPS. We follow, in the source and with examples, how mod_ssl hooks into startup and every connection, slips OpenSSL into the filter chain with connection filters and home-made BIOs, swaps the virtual host and certificate based on SNI, and hands SSL_* variables to other modules.
-
Digging into Apache httpd's Source 16: Logging — Access Logs, Error Logs, and Wrapping Up the Series
How httpd's two kinds of log are actually written, followed through the source. How one ap_log_rerror() call is filtered by a macro before anything runs, how module_index lets LogLevel differ per module, how ErrorLogFormat and LogFormat strings become arrays of function pointers, exactly when the log_transaction hook fires, and how a piped log program is restarted — all checked with examples, followed by a look back over the 16-part series.
-
Digging into Tomcat's Source 1: How I Read It with AI, and the Map
The first post of a series that reads the Tomcat source — it starts by showing what Tomcat is, how it works and what it is for, with diagrams and a 3-minute hands-on run. It covers which version is read (the main branch, 12.0.0-M1) and how it differs from the stable 11.0.x, which Servlet spec version it implements, how to get the same source and build it with Ant in Docker, a folder map of the roughly 740,000-line codebase, the big road one request travels from the Acceptor to your servlet, and how AI-written notes are re-checked against the source.
-
Digging into Tomcat's Source 2: The Big Picture — Server, Service, Connector and the Four Containers
A map for anyone opening the Tomcat source for the first time. We look at which class each server.xml element (Server, Service, Connector, Engine, Host) turns into, how the four containers Engine, Host, Context and Wrapper are linked as parents and children, why the connector (Coyote) and the servlet container (Catalina) are kept apart and joined by a single Adapter, and where Jasper, EL, JULI and Naming live — all checked against the real source and examples I actually ran.
-
Digging into Tomcat's Source 3: Lifecycle and Startup — What Happens After catalina.sh start
We follow the source from catalina.sh start to a running server — Bootstrap building class loaders, Catalina turning server.xml into a tree of objects with the Digester, and every component being initialized and started by the same Lifecycle state machine. With a listener and small examples we check the order in which parents start their children, why the server still comes up when a connector fails, and what the shutdown port 8005 does.
-
Digging into Tomcat's Source 4: Class Loaders — Each Web App Sees Its Own Classes
Why can two web apps in a single Tomcat process use different classes with the same name? We find the answer in the source — the common, server and shared loaders that Bootstrap builds from catalina.properties, the per-app WebappClassLoaderBase whose loadClass() turns parent delegation around, filter() which picks out exceptions such as jakarta.servlet, the delegate setting, and reloadable reloads with their thread-leak warnings — and check each one on a Tomcat built from the reference commit.
-
Digging into Tomcat's Source 5: The Connector and the NIO Endpoint — Which Threads Take a Connection
We follow in the source how one line of server.xml, <Connector port="8080">, becomes three objects — Connector, Http11NioProtocol and NioEndpoint — and how the Acceptor, the Poller and the worker threads share the work of a single connection inside them. With jstack and a raw-socket client we check how a keep-alive connection lets go of its thread between requests, how Tomcat's thread pool (TaskQueue) behaves differently from the JDK's, and where maxConnections, acceptCount and maxThreads take effect. We also compare with 11.0.x and see that the NIO2 connector is gone from the main branch.
-
Digging into Tomcat's Source 6: Reading an HTTP/1.1 Request — From Bytes to a coyote Request
We follow, through the real source, how bytes read from a socket are split into a request line and headers inside the service() loop of Http11Processor and become a coyote Request. By sending raw requests we also see MessageBytes, which delays making Strings, the maxHttpHeaderSize and maxHeaderCount limits, how a bad request turns into an HTML 400 page, and where keep-alive and chunked responses are decided.
-
Digging into Tomcat's Source 7: CoyoteAdapter and the Mapper — Which Servlet Gets the Request
We follow CoyoteAdapter, the bridge that carries a fully read HTTP request from Coyote into Catalina; postParseRequest(), which decodes and normalizes the URI on the way; and the order of rules the Mapper uses to pick a Host, a Context and a Wrapper. Six servlets show the mapping priorities, curl shows what happens to %2F, .. and ;jsessionid, and a request object held too long shows why RequestFacade exists.
-
Digging into Tomcat's Source 8: Pipelines and Valves — The Processing Chain on Every Container
What does one <Valve> line in server.xml become in the source? We look at the pipeline each of Engine, Host, Context and Wrapper owns, the four basic valves at the ends that pick the next container, how a valve stops a request by not calling the next one, ErrorReportValve that draws the error page, the access log that is only written after the response has gone out, and the RemoteIpValve trap — checking it all against the source with our own valves hung on three containers.
-
Digging into Tomcat's Source 9: The Filter Chain and the Servlet Call — Until service() Runs
At the very end of the valve chain, Tomcat takes out a servlet instance, picks the filters that match this request, puts them in an array, and walks that array one step at a time until it finally calls service(). We follow this in the source, and use examples to check when a servlet's init() runs, how filter order is decided, how forward differs from include, and how an exception becomes an error page.
-
Digging into Tomcat's Source 10: Deploying a Web App — From a WAR to a Running Context
We follow in the source what happens inside Tomcat when you drop a WAR file into the webapps folder. HostConfig checks the folder every 10 seconds and creates a Context, ContextConfig merges web.xml, conf/web.xml and annotations and finds ServletContainerInitializers, and StandardContext.startInternal() wakes up SCIs, listeners, filters and servlets in a fixed order. We also look, from Tomcat's side, at where Spring's SpringServletContainerInitializer gets called.
-
Digging into Tomcat's Source 11: Sessions — How JSESSIONID Is Made and Removed
We follow, in the source, what Tomcat does behind one call to request.getSession() — where it reads the session ID, how it makes a new one, and when it throws it away. Cookies and ;jsessionid, the 32-character ID built from SecureRandom, the two kinds of expiry (on a request and on a background thread), and StandardManager saving sessions to a file across restarts, each checked with a small example.
-
Digging into Tomcat's Source 12: Async Servlets and Non-Blocking I/O — Letting Go of the Thread
A servlet that calls startAsync() returns without finishing its response, and the request thread goes back to the pool. We follow, in Tomcat's source and with examples that print thread names, the AsyncStateMachine that holds the request in the meantime, the path complete() and dispatch() take back to a container thread, the timeout check that runs once a second, and how a ReadListener works together with the Poller.
-
Digging into Tomcat's Source 13: Jasper — How a JSP Becomes a Servlet
What happens inside Tomcat the first time you request hello.jsp? We follow the Jasper source as JspServlet takes the request, reads the JSP file into a node tree, writes it out as Java source, compiles it with ecj, leaves a .class in the work directory, and then calls that class like any servlet. We open the generated .java file, and use examples to see when an edited JSP gets recompiled (development, modificationTestInterval, checkInterval).
-
Digging into Tomcat's Source 14: Authentication and Realms — How security-constraint Is Enforced
We follow how web.xml's security-constraint and login-config, server.xml's Realm and tomcat-users.xml connect in the source. Where the Authenticator valve is plugged in when a web app starts, the order of checks in AuthenticatorBase.invoke(), the four searches that pick the constraints for a URL, how BASIC and FORM differ, and what the Realm and the CredentialHandler do — all checked against the real source and real 401, 403 and 303 responses.
-
Digging into Tomcat's Source 15: HTTP/2 and WebSocket — Protocols Reached by Upgrading
We follow, through Tomcat's source, how a connection that started as HTTP/1.1 turns into HTTP/2 or WebSocket. UpgradeProtocol and UpgradeToken, the upgrade handling in Http11Processor, Http2UpgradeHandler carrying many streams on one connection, and WebSocket's WsFilter and WsHttpUpgradeHandler, which come in through a single filter — all checked by hand with h2c requests and the JDK WebSocket client.
-
Digging into Tomcat's Source 16: Embedded Tomcat, JMX and Logging — and Wrapping Up the Series
What the startup/Tomcat class builds for you when you start Tomcat from code without server.xml, and what it does not; how every component registers an MBean for JMX; and how JULI's ClassLoaderLogManager gives each web app its own logging configuration — all checked against the source and with examples, followed by a look back over the 16-part series.
-
Digging into Spring's Source 5: How @Autowired Picks the Bean to Inject — Dependency Resolution
Which bean Spring puts into an @Autowired field or constructor, how it finds that bean, and how it chooses when there are several — followed through the real source. Candidate lookup, the precedence of @Primary, @Qualifier and name matching, List and Map injection, the constructor selection rules, and what happens when nothing is found, each checked by running an example.
-
Digging into Spring's Source 6: Lifecycle Callbacks and PostProcessors — Where You Can Step into the Container
When exactly, and in what order, are BeanFactoryPostProcessor, BeanPostProcessor and its sub-interfaces, the Aware interfaces, @PostConstruct, InitializingBean, init-method, @PreDestroy, DisposableBean, destroy-method, SmartInitializingSingleton and SmartLifecycle called? Checked against the real source and real program output, with a look at why there are so many different hooks.
-
Digging into Spring's Source 7: The Twelve Steps of refresh() — the Order in Which the Container Starts
Behind the single line new AnnotationConfigApplicationContext(...) runs refresh(). This post opens its twelve steps one by one, and checks in the real source and with runnable examples what ApplicationContext adds on top of BeanFactory, why there are two branches (GenericApplicationContext and AbstractRefreshableApplicationContext), and what happens when startup fails or when close() is called.
-
Digging into Spring's Source 8: How @Configuration and @ComponentScan Become Blueprints
How a configuration class (@Configuration), its @Bean methods, and the @Component classes found by @ComponentScan turn into bean blueprints (BeanDefinition), followed step by step for someone new to Spring. How ConfigurationClassPostProcessor reads blueprints to add more blueprints, how component scanning reads class files without loading them, and why a @Configuration class is wrapped with CGLIB so that calling a @Bean method twice still gives you the same object — all checked against the real source and runnable examples.
-
Digging into Spring's Source 9: @Conditional, @Profile and @Import — Choosing Beans by Condition
How @Conditional and @Profile, which register a bean only when a condition holds, and @Import, which pulls in other configuration, are evaluated inside the container — when and how — explained for someone new to Spring. Why a condition is evaluated three times, the three kinds of @Import, what DeferredImportSelector "defers" and why, the ImportStack that catches circular imports, and why every @EnableXxx is just @Import in disguise, all checked against the real source and small runnable examples.
-
Digging into Spring's Source 10: How an AOP Proxy Gets Built — the JDK Way and the CGLIB Way
How a single ProxyFactory.getProxy() call decides between a JDK dynamic proxy and a CGLIB proxy, how each one is built, and how a call on the proxy travels through the interceptor chain to the target object — all checked against the real source. Also why calling your own method from inside the class skips the advice, and the trap with final methods, shown with runnable examples.
-
Digging into Spring's Source 11: How @Aspect Gets Discovered and How the Advice Chain Runs
What the single line @EnableAspectJAutoProxy registers in the container, how an @Aspect class is turned into advisors, how the container decides which beans to wrap in a proxy, and in what order @Around, @Before, @After, @AfterReturning and @AfterThrowing run when a proxied method is called — all checked against the real source and real example output.
-
Digging into Spring's Source 12: Events, Environment, Resource — Three Abstractions Around the Container
The three things ApplicationContext adds on top of BeanFactory — events (publishing and listening), Environment (configuration values and profiles) and Resource (reading files and the class path) — explained for someone new to Spring, with where each one hooks into refresh(). The journey of one publishEvent call, why a system property beats a properties file, and where the "classpath:" prefix turns into a class, all checked against the real source and run as examples.
-
Digging into Spring's Source 13: ResolvableType and MergedAnnotations — How Spring Reads Types and Annotations
How Spring finds the String in List<String> (ResolvableType), how it reads annotations stacked on annotations and @AliasFor as one merged view (MergedAnnotations), and how it reads a .class file without loading the class (MetadataReader) — checked against the real source with runnable examples, for someone new to Spring.
-
Digging into Spring's Source 14: Type Conversion and BeanWrapper — From a String to an Object's Property
How the string "8080" from a configuration file ends up in a bean's int field, followed through the source. Why Spring has both PropertyEditor and ConversionService and which one it asks first, how BeanWrapper walks a path like "address.city", and where the container plugs these two in — explained for someone seeing Spring for the first time.
-
Digging into Spring's Source 15: SpEL — How an Expression Is Parsed and Evaluated
How a string like "#{T(java.lang.Math).max(a, 3) + 1}" becomes tokens, then a tree, then a value, followed through the source. How operator precedence is expressed as the call order of parser methods, why there are two evaluation contexts (StandardEvaluationContext and SimpleEvaluationContext), the compiler mode that turns an expression into bytecode, and the path a @Value("#{…}") takes inside the container — explained for someone seeing Spring for the first time.
-
Digging into Spring's Source 16: Utilities and AOT — the Shared Toolbox and Computing Ahead of Time
Why spring-core carries its own utilities (Assert, ClassUtils, ReflectionUtils, three special maps, OrderComparator) that every module uses, where they appear and where they bite, and how Spring 6's AOT "computes ahead" by turning a container into Java source and RuntimeHints at build time — all checked against the real source and running examples.
-
Digging into Spring's Source 17: @Transactional — Who Opens a Transaction and Who Closes It
Put @Transactional on a method and somebody opens a transaction, rolls it back on an exception and commits it otherwise — this post follows who that somebody is, in the real source. The three interfaces PlatformTransactionManager, TransactionDefinition and TransactionStatus, the beans that @EnableTransactionManagement registers, the order in which TransactionInterceptor wraps a call, where propagation branches, why the connection is bound to a ThreadLocal, and traps such as "checked exceptions do not roll back" and "the outer method gets an UnexpectedRollbackException when the inner one fails", all checked with examples.
-
Digging into Spring's Source 18: JdbcTemplate — the Template-Callback Pattern and Exception Translation
Plain JDBC repeats the same chores on every query — get a connection, build a Statement, handle the exception, close everything. This post follows, in the source, how JdbcTemplate gathers those chores into one place and leaves your code only a callback; how it shares a connection with @Transactional; and how a SQLException turns into a Spring exception such as DuplicateKeyException, including which translator is the default in 7.x. Written for someone seeing Spring for the first time.
-
Digging into Spring's Source 19: ORM and R2DBC Integration — JPA Support and Reactive Transactions
How JPA's EntityManagerFactory becomes a bean, why the EntityManager you get from @PersistenceContext is safe to share between threads, how JdbcTemplate ends up on the same connection under JpaTransactionManager, and where a @Repository's exceptions get translated — all checked in the source. Then the reactive side, where there is no thread to hang a transaction on, and R2DBC transactions ride in the Reactor Context instead.
-
Digging into Spring's Source 20: The spring-web Foundation — HttpMessageConverter, WebApplicationContext, Filters
A first-timer's read of spring-web, the module that both Spring MVC and WebFlux stand on. The interfaces that model an HTTP message as an object, HttpMessageConverter turning a body into a Java object and the rule for picking one, the spot where the Spring container starts inside a Servlet container (ContextLoaderListener), and how a filter that runs only once per request is built — all checked against the source and with runnable examples.
-
Digging into Spring's Source 21: DispatcherServlet — How One Request Becomes a Response
We follow a single HTTP request through Spring MVC by reading the source of DispatcherServlet. Written for people new to Spring, it covers how the servlet sets up its own context and strategy objects at startup, and the order in which doDispatch finds a handler, calls it, and weaves in interceptors and exception handling.
-
Digging into Spring's Source 22: Handler Mappings and Adapters — How @RequestMapping Methods Are Found and Called
When and where a method annotated with @GetMapping gets registered, which of several candidate methods wins for a request, who fills in the chosen method's arguments, and who turns its return value into a response — followed through the source of RequestMappingHandlerMapping and RequestMappingHandlerAdapter. Examples show the two different "ambiguous mapping" errors and exactly where a custom argument resolver slots in.
-
Digging into Spring's Source 23: Exception Handling, Interceptors, Views — Before, After, and the End of a Request
How HandlerInterceptors that run around a controller method are lined up and why only the ones whose preHandle passed get afterCompletion, in what order an exception thrown by a controller travels through the HandlerExceptionResolvers until it reaches an @ExceptionHandler method, and how a single view name goes through ViewResolver and View to end in a forward to a JSP — all checked against the real source and the output of small examples.
-
Digging into Spring's Source 24: WebFlux — DispatcherHandler, Functional Endpoints, and WebClient
Follow the path a request takes through Spring WebFlux, in the source. We look at how WebFilter and WebExceptionHandler are woven together under HttpHandler, at DispatcherHandler as the reactive counterpart of DispatcherServlet, at how annotated controllers and functional endpoints share one road, and at the filter chain inside WebClient.
-
Digging into Spring's Source 25: WebSocket and Messaging — How a STOMP Message Flows
Follows one STOMP message from the browser through an @MessageMapping method and out to every subscriber, in the source. The Message, channel and handler abstractions of spring-messaging, the handshake in spring-websocket, and the three channels and simple broker that connect them — explained for someone who has never seen WebSocket before.
-
Digging into Spring's Source 26: spring-test — SpringExtension, TestContext, MockMvc
Put one annotation on a test class and a container starts, beans get injected, and transactions roll back. We check in the real source how SpringExtension connects JUnit to Spring, how the context cache and its key start a context only once for the same configuration, how test transactions work, and how MockMvc runs the real DispatcherServlet without a server.
-
Digging into Spring's Source 27: Caching, Scheduling, Validation, Resilience — Features Built on Top of the Container
@Cacheable, @Scheduled, @Async, @Validated and @Retryable look like separate features, but inside they repeat one pattern. For readers new to Spring, this post checks in the source what each switch annotation registers, which post-processor wraps the bean, and which interceptor intercepts the call, and then wraps up the whole series.
-
Digging into Spring's Source 1: How I Read It with AI, and the Map
The first post of the Spring source-reading series, starting with what Spring is, how it works and what it is for — shown first through the smallest possible example, pictures and a three-minute exercise. It then explains how an 840,000-line codebase is split into small units and read with the help of AI, how to get exactly the same source, and the order the reading will follow.
-
Digging into Spring's Source 2: The Big Map — Modules and Packages
A map for anyone opening the Spring Framework repository for the first time. What each of the 22 modules does, how they depend on one another, what lives inside spring-core, spring-beans, spring-context and spring-aop, and the big picture of container start-up — all checked against the real build files and source.
-
Digging into Spring's Source 3: The BeanFactory Hierarchy and BeanDefinition
Before Spring builds an object it prepares a blueprint for it (BeanDefinition), and it hands objects out through a front desk (BeanFactory) that is split into several small interfaces. This post follows both through real source excerpts and diagrams, written for someone new to Spring.
-
Digging into Spring's Source 4: How a Bean Gets Created — the Creation Flow and the Three-Level Cache
What happens inside Spring when getBean() is called once, followed step by step for someone new to Spring. The cache lookup, the three creation steps (instantiate, inject, initialise), and how three maps untangle a circular reference where A needs B and B needs A — all checked against the real source.