Java

23 posts

datadog3 min readCurated summary

How we improved APM Java startup by encoding a prefix trie as a JVM constant

Startup performance is critical for users, developers, and cloud costs, but Java APM instrumentation must balance observability against the overhead of transforming classes. Datadog reduced class-matching overhead by 30% over four years by optimizing the first filtering stage: matching class-name prefixes. Its main innovation was encoding a prefix trie as a single JVM string constant, avoiding the startup cost of constructing a conventional trie. ## Java Instrumentation and Class Matching - Java APM uses the Java Instrumentation API to intercept and transform classes as they load. - Instrumentation adds method advice that records method execution and propagates tracing context. - Instrumenting every method would be too expensive, so APM first identifies valuable classes. - Applications may load tens or hundreds of thousands of classes, making efficient filtering important. - Class-name and package-prefix checks are cheaper than structural or hierarchy-based checks because they avoid parsing class files. - Datadog therefore begins with a curated ignore list of class and package prefixes. ## Startup Constraints in `premain` - Agents register transformers in the JVM’s `premain` phase, before the application’s `main` method. - At this point: - Few classes have been loaded. - The JIT compiler is cold or unavailable, especially on Java 8. - Code runs interpreted and unoptimized. - Loading or calling certain JDK classes can have irreversible side effects. - For example, touching `java.util.logging` initializes `LogManager`, potentially preventing an application from configuring its own logging manager later. - These constraints make ordinary data loading, parsing, and object construction undesirable during startup. ## Replacing a Hand-Written Matcher with a Trie - Datadog’s earlier matcher used a complex nested code structure to represent prefixes. - Although flexible, it was difficult to maintain and required special optimizations for Java 8 startup. - A trie was a natural replacement because it shares common characters among prefixes and supports efficient lookup. - A conventional trie would require: - Locating and reading a resource. - Parsing its contents. - Constructing trie nodes. - Loading additional code or dependencies. - Those operations would impose unacceptable costs during `premain`. ## Encoding the Trie as a JVM Constant - Datadog created `ClassNameTrie`, which stores the entire prefix trie in a Java string constant. - The JVM loads the encoded data with a single `ldc` bytecode instruction. - This approach avoids resource I/O and runtime trie construction. - Embedding the data in the class also makes it resilient to repackaging. - The compact representation improves cache locality and reduces startup work. ## Compact Node Representation - Java strings contain 16-bit `char` values, allowing each character to encode one of 65,536 possible values. - Each trie node stores: - A character indicating the number of branches. - Sorted branch characters, enabling binary search. - One value character per branch. - Value characters encode different outcomes: - **Leaf:** returns a definitive result and ends the search. - **Bud:** records a possible result but permits further matching. - **Inline segment length:** indicates that additional prefix characters are stored directly. - Buds and leaves can include a **glob bit**, allowing a match to apply even when extra characters remain in the class name. - The encoding reserves the remaining value range for match results, with a maximum stored value of 8,191. The broader lesson is that startup-sensitive JVM code may benefit from moving computation into class-loading time and representing lookup structures in compact constants. For Java agents, precomputed, dependency-free data structures can deliver trie-like performance without the initialization and JIT costs of building them at runtime.

Read original(opens in new tab)
github1 min readCurated summary

Using the GitHub Copilot SDK for Java

Ed Burns is a Principal Software Engineer focused on bringing idiomatic Java experiences to Microsoft and GitHub technologies. He has worked with Java since 1997 across client, server, cloud, and artificial intelligence applications. ## Professional Focus - Works at Microsoft and GitHub. - Concentrates on making Java development feel natural and idiomatic within their technologies. ## Experience - Has used Java since 1997. - His experience spans: - Client-side development - Server-side systems - Cloud technologies - Artificial intelligence No specific blog topic or technical argument is included in the provided content.

Read original(opens in new tab)
datadog3 min readCurated summary

Unbiased Java CPU profiling with JFR in JDK 25

Java Flight Recorder (JFR) provides low-overhead, production-safe diagnostics, but its `ExecutionSample` event can produce biased CPU profiles because it samples JVM-observed runnable threads rather than strictly measuring CPU time. For CPU-bound workloads—particularly reactive applications—this may obscure the real hotspots. Modern profilers therefore combine JFR with JVMTI, `SIGPROF`, `AsyncGetCallTrace`, and JVM-internal techniques, while the Java ecosystem works toward a supported CPU-sampling mechanism. ## How Sampling Profilers Work - Continuous profilers repeatedly capture stack traces and aggregate them to reveal recurring behavior. - CPU profilers commonly sample at fixed intervals, such as every 20 milliseconds. - **CPU-time sampling** highlights code actively consuming processor cycles. - **Wall-clock sampling** reveals latency sources, including I/O waits, lock contention, and blocked threads. - Other profilers trigger on events such as allocations, garbage collection, thread parking, or lock contention. - Regardless of the trigger, profilers capture a stack, associate it with an event, and aggregate the results. ## Limitations of JFR’s `ExecutionSample` - JFR is integrated into the JVM and designed for low-overhead, always-on production use. - Its `ExecutionSample` event captures stacks from a rotating subset of runnable threads. - CPU-heavy threads tend to appear more often, but samples are not strictly proportional to actual CPU consumption. - This can lead to incomplete or biased results on CPU-saturated systems. - Reactive applications are a notable example: their scheduling behavior can cause thread CPU usage and hotspots to be underrepresented. ## CPU Sampling with `AsyncGetCallTrace` - JVMTI agents can use operating-system signals such as `SIGPROF` to sample threads according to CPU time. - The signal handler invokes HotSpot’s `AsyncGetCallTrace` to walk Java stacks asynchronously. - This approach avoids safepoint bias and can capture stacks during arbitrary execution states. - Tools such as `async-profiler` use this technique to produce profiles that more closely match actual CPU usage. - The drawback is that `AsyncGetCallTrace` is an unsupported internal JVM API. - Under heavy load, it can occasionally fault, requiring profilers to add extensive safeguards. - Datadog also uses **vmstructs walking**, which reads internal JVM metadata to recover stack and runtime information unavailable through standard APIs. ## The Safety–Accuracy Tradeoff - JFR offers stability, structured runtime telemetry, and low overhead. - `AsyncGetCallTrace` and vmstructs walking offer more accurate CPU sampling. - Relying on JVM internals creates maintenance and reliability risks because those interfaces are not officially stable. - Consequently, modern profilers combine JFR with unsupported sampling mechanisms rather than choosing only one approach. ## Toward a Supported CPU Profiling Event - Datadog, SAP, Amazon, and OpenJDK contributors recognized that this limitation affected the broader profiling ecosystem. - JFR was already the natural foundation for safe, continuous profiling. - The missing capability was a first-class CPU sampling event that could provide accurate CPU-based results without depending on unsupported JVM internals. - Datadog participated in OpenJDK discussions to explain why existing sampling was insufficient and to help improve the platform’s profiling foundation. Ultimately, accurate production CPU profiling requires both JFR’s safety and CPU-time-based sampling. A supported JFR CPU profiling event would remove the ecosystem’s dependence on fragile JVM internals while preserving the low-overhead behavior needed for continuous use.

Read original(opens in new tab)
gitlab3 min readCurated summary

Modernize Java with Cursor and GitLab

The post argues that modernizing Java 8 to Java 21 should be handled as a series of small, reviewable changes rather than one large AI-generated merge request. Cursor is effective for bounded coding tasks, while GitLab provides the planning, CI/CD, security, review, and lifecycle context needed to make those changes safe. The recommended approach is to begin with a focused test fix, establish quality gates, and then modernize one application boundary at a time. ## AI-Assisted Java Modernization - Java modernization affects the build, runtime, dependencies, APIs, concurrency, tests, containers, and production behavior. - A single broad prompt can produce an oversized merge request that is difficult to validate or review. - Cursor works best when given a focused issue, such as one failing test or one bounded implementation problem. - GitLab complements Cursor with: - Durable planning through epics and issue hierarchies - GitLab MCP context inside Cursor - CI/CD and security scanning - Code Review Flow and Developer Flow - Code-owner approvals and impact analysis - Cross-service testing and review evidence ## The Java HTTP Metrics Collector - The tutorial uses Tanuki IoT Platform’s Java HTTP metrics collector. - The collector: - Checks HTTP health and maintenance endpoints - Records response status and timing metrics - Sends readings to a Rust metrics-store backend through `POST /api/metrics` - This creates a realistic boundary for modernization because both the Java client and Rust backend contract must continue working. ## Project Setup and Guardrails - Required tools include Cursor, Java 8 and Java 21, Maven, Docker, Docker Compose, and GitLab MCP. - GitLab Duo Code Review Flow, Developer Flow, and an impact-analysis flow should be enabled for the project. - The repository includes `AGENTS.md`, which provides Cursor with project structure, instructions, and Maven test commands. - The workflow begins by importing the GitLab project, cloning it, and opening it in Cursor. ## Fixing the Failing End-to-End Test - The collector allows users to configure an expected HTTP status code. - The implementation incorrectly treats every 2xx response as successful and rejects configured responses such as `503`, even when they are expected. - An existing end-to-end test exposes the mismatch, but the CI job is initially allowed to fail, turning the failure into ignored background noise. - Cursor is prompted to: - Analyze the problem first - Trace the configuration through `HttpCollector` - Fix the implementation - Run the focused tests and the full Maven test suite - Once the fix passes, Cursor creates a branch and merge request. - The formerly non-blocking end-to-end job can then become a required check once it is deterministic and green. ## Review and Merge Controls - Each merge request triggers CI/CD, tests, and security scanning. - GitLab Duo Code Review evaluates the change against Java-specific project instructions. - Concrete review findings are addressed through Developer Flow before merging. - The merge request remains the central collaboration and decision point, even when Cursor performs most of the implementation work. - Fixing the test first establishes a behavioral baseline without combining it with the Java runtime migration. ## Planning the Java 21 Migration - The Java 8-to-21 migration is treated as a larger, planned effort rather than an isolated coding task. - The modernization epic contains: - Child work items - Team discussions - Research merge requests - Pipeline history - Dependencies - Security findings - This project context gives the agent information beyond the local source code and helps define the quality gates required before changing production behavior. The practical recommendation is to use Cursor for fast, narrowly scoped implementation while relying on GitLab to provide durable planning, automated evidence, and consistent review controls. This combination allows teams to modernize incrementally without sacrificing safety or reviewability.

Read original(opens in new tab)
aws3 min readCurated summary

Proactively reduce tech debt autonomously with AWS Transform – continuous modernization (preview) | Amazon Web Services

AWS is previewing AWS Transform – continuous modernization, a capability designed to continuously detect, prioritize, and remediate technical debt across thousands of repositories. It replaces fragmented, manual tooling with configurable analysis, automated pull requests, and current compliance visibility. The goal is to help engineering and platform teams keep codebases modern as dependencies, frameworks, runtimes, and security requirements evolve. ## Continuous Technical Debt Analysis - Scans connected repositories against configurable organizational baselines. - Produces findings within hours, including: - End-of-life dependencies - Deprecated frameworks - Security and code-quality issues - Organization-specific technical debt patterns - Teams can define custom policies for approved libraries, internal standards, deprecated components, or preferred coding patterns. - Findings provide a current view of which repositories are behind baseline, by how much, and which files or components are affected. - This reduces reliance on manual status reports and periodic compliance checks. ## Autonomous Remediation - AWS Transform can automatically generate pull requests for affected repositories. - Built-in transformations support common tasks such as: - Java version upgrades - SDK migrations - Library updates - Custom transformations can be created for organization-specific modernization needs. - Teams retain control by reviewing and merging the generated pull requests or applying their own fixes. - Continuous analysis verifies when repositories return to compliance without requiring manual confirmation. ## Integrated Security Remediation - Integration with AWS Security Agent brings source-code security vulnerabilities into the same workflow. - Security findings appear alongside other technical debt in a prioritized list. - Remediation is delivered through pull requests rather than separate, disconnected security processes. ## Dashboard and Remediation Campaigns - The AWS Transform web application provides portfolio-level visibility across repositories. - Users can view finding severity, affected files, categories, repositories, and available remediation options. - Remediation campaigns track: - Pull requests created - Pull requests merged - Repositories restored to compliance - AWS Transform supports repositories connected from GitHub and local environments. ## Continuous Mode and Campaign Mode - **Continuous mode** handles recurring maintenance: - Dependency upgrades - Security patches - Runtime updates - Coding-standard enforcement - **Campaign mode** is intended for larger, project-based changes, such as migrating frameworks or upgrading a major runtime across hundreds of applications. - AWS Transform custom remains the flexible option for substantial modernization projects, while continuous modernization focuses on high-volume, ongoing maintenance. AWS Transform – continuous modernization is available in preview through the AWS Transform web application, AWS Transform Kiro Power, MCP, and skills for coding-agent integration. It is most useful for organizations that need automated, organization-wide visibility and pull-request-based remediation for continuously accumulating technical debt.

Read original(opens in new tab)
github3 min readCurated summary

Give GitHub Copilot CLI real code intelligence with language servers

GitHub Copilot CLI can understand code far more accurately when connected to a Language Server Protocol (LSP) server. Without LSP, it relies on grep, package-directory browsing, and bytecode extraction, which can miss types, overloads, and dependencies. The LSP Setup skill automates server installation and configuration for 14 languages, giving the CLI capabilities such as type resolution, go-to-definition, and reference search. ## The Problem with Heuristic Code Understanding - Without an LSP server, Copilot CLI may: - Extract Java JAR files and grep through `.class` files. - Read installed Python packages directly. - Search through TypeScript’s `node_modules`. - These approaches use text and pattern matching rather than semantic analysis. - They often fail to correctly understand: - Generics and overloads. - Transitive types. - Compiled dependencies. - Exact method signatures and symbol relationships. - LSP requests such as `textDocument/definition` return precise source locations, resolved types, and signatures. ## How the LSP Setup Skill Works The skill automates a seven-step process: - **Language selection:** Uses `ask_user` to determine the required language. - **Operating system detection:** Identifies macOS, Linux, or Windows so it can choose the correct installation commands. - **Server lookup:** Reads curated data for 14 languages from `references/lsp-servers.md`. - **Configuration scope:** Supports: - User-wide configuration at `~/.copilot/lsp-config.json`. - Repository-specific configuration at `lsp.json` or `.github/lsp.json`. - Repository configuration takes precedence. - **Installation:** Runs the appropriate package-manager or platform-specific command, such as: - `npm install -g typescript typescript-language-server` - `brew install jdtls` - `rustup component add rust-analyzer` - **Configuration:** Adds a server under the `lspServers` object, mapping commands and file extensions to language identifiers. - **Verification:** Confirms the executable is on `PATH` and checks that the configuration is valid JSON. The skill merges new settings with existing configuration instead of overwriting other servers. It also accounts for transport differences, such as servers requiring `--stdio`. ## Supported Languages and Custom Setup - The skill provides predefined installation and configuration details for 14 languages. - If a language is not included, Copilot CLI can search for a suitable server and guide the user through manual configuration. - Each server configuration specifies: - The executable command. - Optional arguments. - File-extension mappings such as `.java` to `java`. ## Benefits After Configuration With LSP enabled, Copilot CLI can: - Resolve types across external dependencies. - Jump to definitions in third-party libraries. - Find every reference to a symbol. - Display hover documentation for functions, classes, and types. - Reduce unnecessary tool calls and avoid incorrect assumptions about APIs. - Handle larger and more complex coding tasks with IDE-like semantic understanding. ## Getting Started - Download the LSP Setup skill from the Awesome Copilot project. - Extract it into `~/.copilot/skills/`. - Restart Copilot CLI. - Ask the agent to set up LSP for a language, such as Java or Python. - Restart the CLI again, run `/lsp`, and test navigation on a dependency symbol. The practical recommendation is to configure an LSP server for each language used in a project. The setup gives Copilot CLI structured code intelligence instead of forcing it to reconstruct APIs through text searches and binary inspection.

Read original(opens in new tab)
netflix4 min readCurated summary

Scaling ArchUnit with Nebula ArchRules

Netflix’s Nebula ArchRules extends ArchUnit so architectural and API-lifecycle rules can be shared across thousands of Gradle repositories. Unlike AST-based tools, ArchUnit analyzes compiled JVM bytecode, supports multiple JVM languages, and offers a type-safe Java API for authoring and testing rules. The approach helps identify unsafe API usage, technical debt, and deviations from Netflix’s preferred development practices at fleet scale. ## The API Lifecycle Problem - Netflix operates tens of thousands of Java repositories in a polyrepo environment. - A library incident involving a backwards-incompatible change highlighted the difficulty of deciding when deprecated APIs can safely be removed. - Netflix introduced lifecycle annotations: - `@Deprecated` for APIs scheduled for removal - `@Public` for APIs intended for downstream use - `@Experimental` for APIs that may change - Unannotated APIs are treated as internal - The remaining challenge was identifying downstream projects that use internal, experimental, or deprecated APIs incorrectly. - The same tooling could support large migrations, such as major Spring Boot upgrades. ## Why ArchUnit - ArchUnit is an open-source library commonly used within JUnit suites to enforce architectural rules. - It is built on ASM and analyzes compiled JVM bytecode rather than source syntax. - Its main strengths are: - Cross-language JVM support for Java, Kotlin, Scala, and other JVM languages - A fluent builder API for readable rule definitions - A lower-level API for complex custom analysis - Access to class relationships, dependencies, and call sites through its class graph - Standard ArchUnit is primarily designed for one repository, so Netflix created Nebula ArchRules to distribute rules across many Gradle projects. ## Bytecode Analysis vs. AST Analysis - AST-based tools such as PMD inspect source-code structure and can be sensitive to language-specific syntax and syntactic sugar. - Supporting multiple JVM languages may require separate rules for each language. - ASM analyzes the bytecode that will actually execute, regardless of how the source was written. - This makes rules more consistent across Java, Kotlin, Scala, and other JVM languages. ## Rule Authoring - Tools such as PMD and SpotBugs are generally optimized for built-in rules or third-party plugins rather than custom rule development. - PMD custom rules may require difficult-to-maintain XPath expressions and separate tooling for testing. - ArchUnit rules are written as type-safe, fluent Java code. - Rules can be unit tested directly by passing them class references, without running a separate analysis process. - ArchUnit’s class graph provides contextual information about dependencies and call relationships, enabling more sophisticated checks. ## ArchRules Libraries - The Nebula ArchRules Library Plugin adds an `archRules` source set to a Gradle project. - A class implementing `ArchRulesService` exposes a `Map<String, ArchRule>`: - The map key names the rule. - The `ArchRule` defines the constraint using ArchUnit’s API. - Rule code and its dependencies are kept separate from the application’s main code. - Gradle publishes the rules in a separate JAR using the `arch-rules` classifier and an `arch-rules` usage attribute. - Downstream projects must use Gradle Module Metadata to resolve the rules variant. ## Standalone and Bundled Rule Libraries - Standalone rule libraries contain only `archRules` code. - They are useful for: - Enforcing rules around APIs the organization does not own - Checking usage of Java or open-source libraries - Applying generic rules, such as prohibiting use of deprecated APIs - Bundled rule libraries contain both normal library code and rules specific to how that library should be used. - Netflix maintains open-source standalone rule libraries as examples and reusable building blocks. Nebula ArchRules turns ArchUnit from a repository-local testing library into a reusable organization-wide policy mechanism. Teams can publish rules as Gradle artifacts and apply them consistently across JVM projects, making API governance, dependency policies, and architectural standards easier to enforce at scale.

Read original(opens in new tab)
toss3 min readCurated summary

Embracing the Software 3.0 Era

Software 3.0 replaces hand-written rules with natural-language instructions to LLMs, but models alone cannot reliably perform real-world work. The missing piece is the harness: tools, context, and environments that connect an LLM to codebases, commands, databases, and users. Claude Code illustrates how familiar Software 1.0 architecture can guide agent design while adding a new capability—asking humans for judgment when uncertainty arises. ## From Software 1.0 to Software 3.0 - **Software 1.0:** Developers explicitly write logic using languages such as Python, Java, or C++. - **Software 2.0:** Data and training produce neural-network weights that function as the program. - **Software 3.0:** Prompts and natural-language instructions direct LLM behavior. - Karpathy’s central claim is that Software 3.0 is increasingly absorbing both traditional code and trained models. ## Harnesses Make LLMs Useful - A raw LLM cannot independently read a codebase, execute commands, modify files, or access databases. - A **harness** supplies the tools and environment needed to turn model capability into practical work. - Claude Code is presented as a harness for Claude: it transforms a language model into an agent capable of completing and shipping tasks. ## Mapping Agent Concepts to Layered Architecture The terminology of agent systems can be understood through familiar Software 1.0 design patterns: - **Slash commands → Controllers** - They serve as entry points for user requests, such as `/review` or `/refactor`. - **Sub-agents → Service layer** - They coordinate multiple skills to complete a workflow. - Each sub-agent has an independent context and acts as a self-contained unit of work. - **Skills → Domain components** - Each skill should have one focused responsibility, such as reviewing code, generating tests, or writing documentation. - **MCP → Infrastructure or adapters** - MCP provides abstraction boundaries for external systems such as APIs and databases. - **CLAUDE.md → Project constitution** - It records stable project information: technology choices, conventions, and build commands. - Frequently changing task details should be provided through the conversation or injected into an agent’s context instead. ## Agent Design Has Familiar Anti-Patterns Traditional code smells also apply to agent systems: - **Feature Envy:** A skill relies excessively on another skill’s data. - **Duplication:** Prompts are copied across multiple skills. - **Long Method:** A single sub-agent performs an overly long sequence of many skills. - Clear boundaries, single responsibility, and limited coupling remain valuable. ## The Difference: Agents Can Ask Humans Layered architecture generally requires every failure and edge case to be handled through predefined exceptions, policies, or branches. - Traditional code must decide what to do when an unusual case occurs. - An agent using human-in-the-loop interaction can pause and ask the user for clarification. - In this model, exceptions become questions, allowing the agent to continue after receiving a decision. Agents should ask when: - An action is difficult to reverse, such as deletion or deployment. - Several valid options exist without a clear best choice. - The decision has significant consequences. They should proceed automatically when: - The operation is safely repeatable. - Existing conventions provide a clear answer. - The action is easy to undo. ## What Carries Forward into Software 3.0 The new paradigm does not make established engineering practices irrelevant. - Move away from explicitly coding every possible rule and edge case. - Do not reduce LLMs to simple autocomplete tools. - Preserve layered design, single responsibility, abstraction, dependency management, and interface design. - Continue emphasizing testability, debugging, code review, and iterative improvement. The practical approach is to combine Software 3.0’s flexible reasoning with Software 1.0’s architecture and engineering discipline, while giving agents a clear way to involve humans when decisions require judgment.

Read original(opens in new tab)
kakao4 min readCurated summary

From Student to Developer: Learning Server Flow from Lotto Implementation to Legacy Improvement

The post describes Kakao’s 2026 server-engineering onboarding program, which turns uncertainty into practical understanding through structured implementation, testing, and refactoring. Rather than supplying fixed answers, the program repeatedly asks developers to explain their design decisions and assess what their tests protect. Its central lesson is that server development becomes manageable when engineers build clear reasoning, maintainable structures, and safe change processes. ## Onboarding Through Three Stages - The program follows a progression: 1. TDD- and OOP-based implementation 2. Acceptance testing for legacy code 3. Refactoring legacy code - The focus is not only on what to build, but on how to make engineering decisions. - Core goals include: - Designing maintainable structures - Analyzing and safely improving legacy systems - Collaborating effectively, including responsible AI usage - Although originally designed for server developers, the program expanded to frontend, Android, and iOS engineers because engineering principles apply across technology stacks. ## Learning Through Questions and Collaboration - Participants were repeatedly asked: - Why was this design chosen? - Does this object truly own this responsibility? - What behavior does this test protect? - Daily meetings, pair programming, troubleshooting discussions, and PR reviews made development a collaborative activity. - The program aimed to develop engineers who could explain and defend their designs, rather than merely produce working code. ## Mission 1: Building a Lottery Game with TDD and OOP - The first assignment implemented: - Automatic and manual lottery purchases - A fixed ticket price of 1,000 won - Winning-statistics calculations - Constraints encouraged better design: - One level of indentation - Methods limited to 10 lines - Primitive values wrapped in value objects - First-class collections - Avoiding `else` through early returns - TDD required tests to be written before implementation. ### Making Randomness Testable - Random lottery-number generation initially made tests unpredictable and tightly coupled to concrete implementations. - The solution was to: - Introduce a number-generation interface - Inject the generation strategy - Create a separate test generator - This made test results controllable and encouraged a more flexible design. ### Considering Value Objects and Caching - The team also questioned whether identical number values should always create new objects. - This led to discussions about caching and the difference between object identity and value equality. - The main lesson was to evaluate design decisions, not just make the feature work. ## Mission 2: Writing Acceptance Tests for Legacy Code - Participants first protected the existing system before modifying it. - Tests focused on externally observable behavior: - User actions - System responses - State changes - Strong assertions verified not merely that an operation succeeded, but that it produced the correct result. - Cucumber-based BDD expressed scenarios in a form understandable to non-developers, treating tests as shared specifications. ### Achieving Production Parity - To avoid “works on my machine” problems, the test environment was aligned with production: - PostgreSQL replaced H2 - Docker standardized execution environments - Gradle tasks automated test execution - Test-data isolation used: - Reverse-order foreign-key deletion - `TRUNCATE ... CASCADE` - Shared cleanup utilities - These measures ensured tests started from consistent, independent states. ## Mission 3: Refactoring Legacy Code Safely - The final mission treated refactoring as training in decision-making, not simply an exercise in clean code. - The central rule was to separate structural and behavioral changes: - Structural changes must preserve behavior. - Behavior changes must avoid unrelated structural modifications. - PR reviews helped identify unintended behavior changes and taught participants to predict and control the effects of modifications. - AI was used during refactoring to accelerate broad code changes, but large changes were difficult to verify, highlighting the need to control scope and validate changes carefully. The onboarding’s practical recommendation is to approach server development through small, explainable decisions: write controllable tests, protect legacy behavior before changing it, separate refactoring from feature changes, and use AI as an assistant rather than a substitute for engineering judgment.

Read original(opens in new tab)
netflix3 min readCurated summary

Optimizing Recommendation Systems with JDK’s Vector API

Netflix’s Ranker service used significant CPU for video serendipity scoring, which compares candidate-title embeddings with a member’s viewing history. The team reduced CPU usage by progressively replacing scalar dot products with batched computation, improving memory layout, reusing buffers, and investigating optimized matrix-multiplication libraries. The main lesson was that mathematical optimization alone is insufficient; allocation behavior, cache locality, SIMD support, and runtime overhead all matter. ## The Serendipity Scoring Hotspot - Each candidate title and history item is represented by a vector embedding. - The service computes cosine similarity between every candidate and every history item. - It selects the maximum similarity and converts it into a novelty score: - `serendipity = 1.0 - maxSimilarity` - The original implementation performed `M × N` individual dot products, creating: - Sequential computational work - Repeated embedding lookups - Scattered memory access - Poor cache locality - This logic consumed roughly 7.5% of CPU per Ranker node. - Although 98% of requests contained one video, large batch requests represented about half of the total videos processed. ## Batching Similarity Computations - The team reorganized the calculation as matrix multiplication: - Candidate embeddings form an `M × D` matrix. - History embeddings form an `N × D` matrix. - Rows are normalized to unit length. - Similarities are computed as `C = A × Bᵀ`. - This replaces many separate dot products with one larger operation better suited to CPU-optimized kernels. - The implementation added `batchEncode()` while preserving the existing `encode()` path for single-video requests. ## Why the First Batched Version Regressed - Initial canary tests showed a 5% performance regression. - The batched implementation created `double[][]` arrays for candidates, history, and results on every request. - These allocations: - Increased garbage-collection pressure - Used non-contiguous memory - Added pointer chasing and reduced cache efficiency - The matrix multiplication itself was scalar Java code and did not exploit SIMD hardware. - Batching therefore introduced overhead without delivering corresponding compute gains. ## Flat Buffers and Thread-Local Reuse - The team replaced multidimensional arrays with flat `double[]` buffers in row-major order. - Contiguous storage improved predictability and cache locality. - A `ThreadLocal<BufferHolder>` was used to retain reusable candidate, history, and scratch buffers per thread. - Buffers grow when necessary but do not shrink, avoiding repeated allocations while preventing cross-thread contention. - This reduced GC pressure and made batch performance more stable. ## Evaluating BLAS - BLAS appeared promising in isolated microbenchmarks but did not provide the expected production improvement. - The default `netlib-java` configuration used F2J, a Java implementation rather than truly native BLAS. - Native BLAS introduced setup costs and JNI transition overhead. - Java’s row-major data layout also created an impedance mismatch with common BLAS expectations.

Read original(opens in new tab)
gitlab2 min readCurated summary

GitLab Duo Agent Platform with Claude accelerates development

GitLab Duo Agent Platform integrates external AI models such as Anthropic’s Claude and OpenAI’s Codex directly into GitLab workflows. Instead of operating as isolated coding assistants, these agents use project context and organizational standards to handle multi-step development tasks. The result is faster delivery, more consistent quality, and less manual work across the software development lifecycle. ## From an Idea to a Working Application - An agent can use an issue’s title and detailed requirements as the foundation for a complete application. - It analyzes project context and related assets, then generates: - Backend Java classes - Frontend HTML, CSS, and JavaScript - Business logic and UI components - Build configuration - The agent creates a merge request containing the implementation for developers to test and refine through natural-language interaction. ## Automated Code Review - Developers can mention the external agent in a merge request to request a review. - The review can cover: - Code strengths and critical issues - Medium- and low-priority improvements - Security risks - Testing gaps and code metrics - Recommendations and an approval status - This provides consistent review coverage while allowing senior developers to focus on architecture and complex decisions. ## Pipeline and Container Image Creation - When a project lacks CI/CD configuration, the agent can generate the required pipeline. - It creates a Dockerfile with a suitable base image for the project’s Java version. - The pipeline can: - Build the application - Build a Docker image - Push the image to GitLab’s container registry - The resulting workflow runs automatically through build, image creation, and deployment stages. ## Broader Impact on Development - External agents remain within GitLab, reducing context switching between development tools. - They can follow project-specific coding standards and understand broader repository context. - Teams can automate work from initial requirements through implementation, review, and deployment. - Developers spend less time on repetitive tasks while maintaining stronger consistency and quality. GitLab presents Duo Agent Platform as a way to turn external AI models into integrated development collaborators. Teams can use it to accelerate coding, automate reviews, and create deployment pipelines while keeping humans focused on validation, architecture, and innovation.

Read original(opens in new tab)
airbnb3 min readCurated summary

My Journey to Airbnb — Anna Sulkina

Anna Sulkina’s career journey moved from hardware diagnostics and frontend development into backend infrastructure and engineering leadership. Her experiences at Twitter taught her to design distributed systems for failure and to build consensus around transformative technologies like GraphQL. She joined Airbnb in 2022 because it aligned her passion for travel with an opportunity to strengthen developer infrastructure, organizational strategy, and engineering collaboration. ## Discovering Technology in Post-Soviet Ukraine - Sulkina grew up in Eastern Ukraine as the Soviet Union collapsed. - Her older brother introduced her to computers by bringing home hardware components and assembling a machine that loaded programs from a cassette player. - Seeing how individual components formed a working system inspired her to pursue technology. ## Learning English While Building Technical Skills - She studied programming at a Ukrainian university before immigrating to the United States. - Although she understood written English and knew how to program, communicating in English was initially more difficult than learning programming languages. - She took ESL classes while studying C++ and Java through Berkeley Extension. - Her first job was in hardware diagnostics at a five-person company. - A language barrier caused her to run out of time on a technical interview, but an interviewer familiar with her Berkeley class gave her another opportunity. - She eventually transitioned from C++ to Java, which became her primary language for many years. ## Moving Down the Stack and Into Leadership - Sulkina’s career progressed from hardware diagnostics to frontend, backend, and infrastructure engineering. - At the same time, she increasingly took on leadership responsibilities. - At Caymas Systems, her manager recognized her leadership potential and showed her the difference effective leadership makes. - At Comcast, she moved from individual contributor to engineering manager. - Coaching engineers, building software collaboratively, and developing high-performing teams convinced her that leadership was the right path. ## Lessons from Twitter’s Distributed Systems - During nearly nine years at Twitter, Sulkina advanced from first-line manager to director. - She worked through major operational events, including the “fail whale” period and the tweetstorm surrounding Ellen DeGeneres’s viral selfie. - Twitter’s transition from a monolith to microservices taught her that failure is inevitable in complex systems. - Resilient distributed systems must be designed to handle failures rather than assuming failures can be prevented. - Her cultural lesson involved turning promising ideas into adopted technologies. - She helped bootstrap Twitter’s GraphQL API, replacing legacy REST services. - The effort required leadership support, cross-team consensus, and stakeholder alignment, but ultimately improved product teams’ development velocity. ## Choosing Airbnb - Airbnb contacted Sulkina in 2022, when she felt ready to move beyond a well-established organization at Twitter. - The company appealed to her because it combined her professional interests with her personal passion for travel; she had been an Airbnb guest since 2013. - Airbnb’s Developer Platform organization had strong work happening in separate silos but needed clearer strategy, direction, and trust across engineering. - Sulkina began by clarifying the organization’s purpose and future direction. - Her early priorities included strengthening the organization, coaching leaders, and creating alignment within the team and with the teams it supported. - Over the following years, this work produced a high-performing organization with clearer strategy, stronger execution, and a focus on delivering business value. Sulkina’s story emphasizes that technical growth, organizational leadership, and personal motivation can reinforce one another. Her experience suggests that successful engineering leaders design for failure, invest in alignment, and use clear strategy to turn fragmented efforts into meaningful platform-wide impact.

Read original(opens in new tab)
metaOriginal article

How AI Is Transforming the Adoption of Secure-by-Default Mobile Frameworks (opens in new tab)

Meta utilizes secure-by-default frameworks to wrap potentially unsafe operating system and third-party functions, ensuring security is integrated into the development process without sacrificing developer velocity. By leveraging generative AI and automation, the company scales the adoption of these frameworks across its massive codebase, effectively mitigating risks such as Android intent hijacking. This approach balances high-level security enforcement with the practical need for friction-free developer experiences. ## Design Principles for Secure-by-Default Frameworks To ensure high adoption and long-term viability, Meta follows specific architectural guidelines when building security wrappers: * **API Mirroring:** Secure framework APIs are designed to closely resemble the existing native APIs they replace (e.g., mirroring the Android Context API). This reduces the cognitive burden on developers and simplifies the use of automated tools for code conversion. * **Reliance on Public Interfaces:** Frameworks are built exclusively on public and stable APIs. Avoiding private or undocumented OS interfaces prevents maintenance "fire drills" and ensures the frameworks remain functional across various OS updates. * **Modularity and Reach:** Rather than creating a single monolithic tool, Meta develops small, modular libraries that target specific security issues while remaining usable across all apps and platform versions. * **Friction Reduction:** Frameworks must avoid introducing excessive complexity or noticeable performance overhead in terms of CPU and RAM, as high friction often leads developers to bypass security measures entirely. ## SecureLinkLauncher: Preventing Android Intent Hijacking SecureLinkLauncher (SLL) is a primary example of a secure-by-default framework designed to stop sensitive data from leaking via the Android intent system. * **Wrapped Execution:** SLL wraps native Android methods such as `startActivity()` and `startActivityForResult()`. Instead of calling `context.startActivity(intent)`, developers use `SecureLinkLauncher.launchInternalActivity(intent, context)`. * **Scope Verification:** The framework enforces scope verification before delegating to the native API. This ensures that intents are directed to intended "family" apps rather than being intercepted by malicious third-party applications. * **Mitigating Implicit Intents:** SLL addresses the risks of untargeted intents, which can be received by any app with a matching intent-filter. By enforcing a developer-specified scope, SLL ensures that data like `SECRET_INFO` is only accessible to authorized packages. ## Scaling Adoption through AI and Automation The transition from legacy, insecure patterns to secure frameworks is managed through a combination of automated tooling and artificial intelligence. * **Automated Migration:** Generative AI identifies insecure usage patterns across Meta’s vast codebase and suggests—or automatically applies—the appropriate secure framework replacements. * **Continuous Monitoring:** Automation tools continuously scan the codebase to ensure compliance with secure-by-default standards, preventing the reintroduction of vulnerable code. * **Scaling Consistency:** By reducing the manual effort required for refactoring, AI enables consistent security enforcement across different teams and applications without slowing down the shipping cycle. For organizations managing large-scale mobile codebases, the recommended approach is to build thin, developer-friendly wrappers around risky platform APIs and utilize automated refactoring tools to drive adoption. This ensures that security becomes an invisible, default component of the development lifecycle rather than a manual checklist.

naverOriginal article

@RequestCache: Developing a Custom Annotation (opens in new tab)

The development of `@RequestCache` addresses the performance degradation and network overhead caused by redundant external API calls or repetitive computations within a single HTTP request. By implementing a custom Spring-based annotation, developers can ensure that specific data is fetched only once per request and shared across different service layers. This approach provides a more elegant and maintainable solution than manual parameter passing or struggling with the limitations of global caching strategies. ### Addressing Redundant Operations in Web Services * Modern web architectures often involve multiple internal services (e.g., Order, Payment, and Notification) that independently request the same data, such as a user profile. * These redundant calls increase response times, put unnecessary load on external servers, and waste system resources. * `@RequestCache` provides a declarative way to cache method results within the scope of a single HTTP request, ensuring the actual logic or API call is executed only once. ### Limitations of Manual Data Passing * The common alternative of passing response objects as method parameters leads to "parameter drilling," where intermediate service layers must accept data they do not use just to pass it to a deeper layer. * In the "Strategy Pattern," adding a new data dependency to an interface forces every implementation to change, even those that have no use for the new parameter, which violates clean architecture principles. * Manual passing makes method signatures brittle and increases the complexity of refactoring as the call stack grows. ### The TTL Dilemma in Traditional Caching * Using Redis or a local cache with Time-To-Live (TTL) settings is often insufficient for request-level isolation. * If the TTL is set too short, the cache might expire before a long-running request finishes, leading to the very redundant calls the system was trying to avoid. * If the TTL is too long, the cache persists across different HTTP requests, which is logically incorrect for data that should be fresh for every new user interaction. ### Leveraging Spring’s Request Scope and Proxy Mechanism * The implementation utilizes Spring’s `@RequestScope` to manage the cache lifecycle, ensuring that data is automatically cleared when the request ends. * Under the hood, `@RequestScope` uses a Singleton Proxy that delegates calls to a specific instance stored in the `RequestContextHolder` for the current thread. * The cache relies on `RequestAttribute`, which uses `ThreadLocal` storage to guarantee isolation between different concurrent requests. * Lifecycle management is handled by Spring’s `FrameworkServlet`, which prevents memory leaks by automatically cleaning up request attributes after the response is sent. For applications dealing with deep call stacks or complex service interactions, a request-scoped caching annotation provides a robust way to optimize performance without sacrificing code readability. This mechanism is particularly recommended when the same data is needed across unrelated service boundaries within a single transaction, ensuring consistency and efficiency throughout the request lifecycle.

naverOriginal article

Naver TV (opens in new tab)

JVM applications often suffer from initial latency spikes because the Just-In-Time (JIT) compiler requires a "warm-up" period to optimize frequently executed code into machine language. While traditional strategies rely on simulated API calls to trigger this optimization, these methods often introduce side effects like data pollution, log noise, and increased maintenance overhead. This new approach advocates for a library-centric warm-up that targets core execution paths and dependencies directly, ensuring high performance from the first real request without the risks of full-scale API simulation. ### Limitations of Traditional API-Based Warm-up * **Data and State Pollution:** Simulated API calls can inadvertently trigger database writes, send notifications, or pollute analytics data, requiring complex logic to bypass these side effects. * **Maintenance Burden:** As business logic and API signatures change, developers must constantly update the warm-up scripts or "dummy" requests to match the current application state. * **Operational Risk:** Relying on external dependencies or complex internal services during the warm-up phase can lead to deployment failures if the mock environment is not perfectly aligned with production. ### The Library-Centric Warm-up Strategy * **Targeted Optimization:** Instead of hitting the entry-point controllers, the focus shifts to warming up heavy third-party libraries and internal utility classes (e.g., JSON parsers, encryption modules, and DB drivers). * **Internal Execution Path:** By directly invoking methods within the application's service or infrastructure layer during the startup phase, the JIT compiler can reach "Tier 4" (C2) optimization for critical code blocks. * **Decoupled Logic:** Because the warm-up targets underlying libraries rather than specific business endpoints, the logic remains stable even when the high-level API changes. ### Implementation and Performance Verification * **Reflection and Hooks:** The implementation uses application startup hooks to execute intensive code paths, ensuring the JVM is "hot" before the load balancer begins directing traffic to the instance. * **JIT Compilation Monitoring:** Success is measured by tracking the number of JIT-compiled methods and the time taken to reach a stable state, specifically targeting the reduction of "cold" execution time. * **Latency Improvements:** Empirical data shows a significant reduction in P99 latency during the first few minutes of deployment, as the most CPU-intensive library functions are already pre-optimized. ### Advantages and Practical Constraints * **Safer Deployments:** Removing the need for simulated network requests makes the deployment process more robust and prevents accidental side effects in downstream systems. * **Granular Control:** Developers can selectively warm up only the most performance-sensitive parts of the application, saving startup time compared to a full-system simulation. * **Incomplete Path Coverage:** A primary limitation is that library-only warming may miss specific branch optimizations that occur only during full end-to-end request processing. To achieve the best balance between safety and performance, engineering teams should prioritize warming up shared infrastructure libraries and high-overhead utilities. While it may not cover 100% of the application's execution paths, a library-based approach provides a more maintainable and lower-risk foundation for JVM performance tuning than traditional request-based methods.