
[{"content":"","date":"9 October 2026","externalUrl":null,"permalink":"/tags/claude-code/","section":"Tags","summary":"","title":"Claude-Code","type":"tags"},{"content":"","date":"9 October 2026","externalUrl":null,"permalink":"/tags/developer-tools/","section":"Tags","summary":"","title":"Developer-Tools","type":"tags"},{"content":"Without a language server attached, Claude edits your code the same way text search does — it can find where a string appears, but it has no idea whether the edit it just made actually type-checks. A code intelligence plugin changes that: Claude Code connects to the same language server your editor uses, over the real Language Server Protocol, and gets two things back — diagnostics after every edit, and an LSP tool for navigating by symbol instead of by grep.\nThat second part is concrete, not vague \u0026ldquo;better understanding\u0026rdquo;: Claude can jump to a symbol\u0026rsquo;s definition, find every reference to it, get type information at a position, list the symbols in a file, search for a symbol by name across the whole workspace, find the implementations of an interface, and trace call hierarchies. All read-only, no permission prompt.\nSetup is the same shape for every language # Install the language server binary, install the matching plugin, then edit a file to confirm it started:\nLanguage Plugin Binary Install the binary Java jdtls-lsp jdtls see the plugin\u0026rsquo;s README Python pyright-lsp pyright-langserver see the plugin\u0026rsquo;s README TypeScript / Next.js typescript-lsp typescript-language-server npm install -g typescript-language-server typescript Then install the plugin itself, from your shell (no session needed — handy in a setup script):\nclaude plugin install jdtls-lsp@claude-plugins-official (swap jdtls-lsp for pyright-lsp or typescript-lsp as needed). Running /plugin install the same way inside a session doesn\u0026rsquo;t install right away — it opens a review panel where you still pick a scope (user, project, or local) before anything\u0026rsquo;s installed. Either path, the install summary tells you what\u0026rsquo;s next: Plugin is now active means nothing more to do, Run /reload-plugins to apply means run that in the open session rather than restarting it.\nConfirm the binary is actually on the PATH of the shell you launched claude from — which jdtls, which pyright-langserver, or which typescript-language-server — before you assume the plugin install is the problem if nothing happens.\nWatching it actually work — Java # Ask Claude to introduce a real type error, then fix it. The first edit after jdtls-lsp is installed starts the server, and you\u0026rsquo;ll see a line under that edit:\nFound 1 new diagnostic issues in 1 files (ctrl+o to expand) That\u0026rsquo;s jdtls reporting a real type error back through the LSP tool — not Claude guessing from reading the file, an actual compiler-grade check. Press Ctrl+O to expand and read it. Fix the error, edit again, and the line updates to reflect the server\u0026rsquo;s live state.\nPython and Next.js, same mechanics # For Python, pyright-lsp gives the identical behavior — pyright-langserver reports diagnostics after each edit and backs the same LSP tool for jump-to-definition and find-references across your modules.\nFor a Next.js project, typescript-lsp covers both .ts and .tsx, so it\u0026rsquo;s one plugin for your API routes and your React components — Claude gets prop-type errors on a component the same way it gets a type mismatch in a route handler, through the same diagnostics line.\nCode intelligence plugins only run in terminal sessions. In a cloud session (claude --cloud), Claude Code doesn\u0026rsquo;t start plugin language servers at all — no diagnostics, no LSP tool, regardless of what\u0026rsquo;s installed. Why bother # The real payoff isn\u0026rsquo;t autocomplete-style convenience — it\u0026rsquo;s that Claude catches its own mistakes before you do. An edit that introduces a missing import or a type mismatch gets flagged in the same turn it happened, instead of surfacing later when you run the build yourself. For a multi-file change, that\u0026rsquo;s the difference between Claude fixing its own regression immediately and you finding it three commits later.\n","date":"9 October 2026","externalUrl":null,"permalink":"/blog/lsp-in-claude-code/","section":"Blog","summary":"Without a language server, Claude finds code the way grep does. With one, it gets real diagnostics after every edit and navigates by symbol instead of by text match.","title":"LSP in Claude Code: Setting It Up for Java, Python, and Next.js","type":"blog"},{"content":"","date":"9 October 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"Every single object you allocate on the JVM heap carries a header before any of its actual fields — identity hash code, GC bookkeeping, lock state, a pointer to its class. That header has been 96 bits since compressed oops became the norm. JEP 534 shrinks it to 64 bits, and as of JDK 27, that shrinkage is on by default instead of something you had to know to ask for.\nWhy the header size matters at all # 96 bits doesn\u0026rsquo;t sound like much until you remember it\u0026rsquo;s paid by every object, not just big ones. A Long wrapper, a linked-list node, an Optional — for small objects the header can be a third or more of the object\u0026rsquo;s total footprint. Heap-dense workloads (lots of small, short-lived objects — which describes a large share of typical Java services) pay that tax proportionally harder than anything else. Smaller headers mean more objects fit in cache lines and in the heap itself, which is where the \u0026ldquo;increasing data locality\u0026rdquo; part of the JEP\u0026rsquo;s stated goal actually comes from — it\u0026rsquo;s not just about bytes saved, it\u0026rsquo;s about how much more of the heap fits where the CPU can reach it quickly.\nHistory # Below is the history of the feature\nJDK JEP Status 24 JEP 450 Introduced as an experimental, opt-in feature 25 JEP 519 Promoted to a full product feature — still opt-in 27 JEP 534 Default on — the old 96-bit layout is still available, just no longer the default Straight from the JEP\u0026rsquo;s own motivation section:\n\u0026ldquo;Since JDK 24, compact object headers have proven their stability and performance. They have been tested at Oracle by running the full JDK test suite. They have also been tested at Amazon and SAP. Amazon runs hundreds of services in production with compact object headers, most of them using backports of the feature to JDK 21 and JDK 17. SAP has already switched to compact object headers by default in their downstream OpenJDK fork, the SapMachine.\u0026rdquo; That\u0026rsquo;s a meaningfully different claim than \u0026ldquo;we benchmarked this in a lab.\u0026rdquo; Two large, separate organizations ran this in production, on their own backports, before the JDK project itself turned it on by default.\nWhat it actually buys you # Numbers from the JEP, unmodified:\nSPECjbb2015 uses 22% less heap space and 8% less CPU time Garbage collections during that same SPECjbb2015 run drop by 15% (holds for both G1 and Parallel) A highly parallel JSON parser benchmark runs 10% faster None of that requires touching application code. It\u0026rsquo;s a JVM flag default flip, not an API.\nFlipping it off, if you need to # The flag itself hasn\u0026rsquo;t changed — only its default value has, which means the meaning of not passing it flipped between JDK 25 and JDK 27:\n# JDK 24-25: had to opt in explicitly java -XX:+UseCompactObjectHeaders -jar app.jar # JDK 27+: on by default; opt out explicitly if you need to java -XX:-UseCompactObjectHeaders -jar app.jar If you have a deployment script or a Dockerfile carrying -XX:+UseCompactObjectHeaders forward from a JDK 24/25 upgrade, it\u0026rsquo;s harmless on JDK 27 — you\u0026rsquo;re just being explicit about a value that\u0026rsquo;s now already the default. But if you have monitoring or tooling that reads raw heap dumps or does anything with object layout assumptions, that\u0026rsquo;s the thing actually worth testing before this upgrade, not the flag itself. What happens if you need the space back later # The JEP reserves four bits of the new 64-bit header up front for Project Valhalla (value types), which is a reasonable thing to want to know before you go build something that assumes every remaining bit is free forever. Its own risk section is short and direct about the headroom that\u0026rsquo;s left:\n\u0026ldquo;If a need for even more bits arises, we can shrink compressed class pointers and identity hash codes even further, using techniques we have already prototyped in Project Lilliput.\u0026rdquo;\nAnd notably: removing the old 96-bit layout entirely is explicitly not a goal of this JEP. If compact headers ever cause a problem for you specifically, the escape hatch stays available — it\u0026rsquo;s just not the path most people will take anymore.\n","date":"28 September 2026","externalUrl":null,"permalink":"/blog/compact-object-headers-by-default-jep-534/","section":"Blog","summary":"Every object on the heap has paid a 96-bit tax since forever. Java 27 shrinks it to 64 bits and turns that on for everyone, not just people who opted in.","title":"Compact Object Headers by Default: JEP 534 in Java 27","type":"blog"},{"content":"","date":"28 September 2026","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"","date":"28 September 2026","externalUrl":null,"permalink":"/tags/jvm/","section":"Tags","summary":"","title":"Jvm","type":"tags"},{"content":"","date":"28 September 2026","externalUrl":null,"permalink":"/tags/performance/","section":"Tags","summary":"","title":"Performance","type":"tags"},{"content":"","date":"25 September 2026","externalUrl":null,"permalink":"/tags/jfr/","section":"Tags","summary":"","title":"Jfr","type":"tags"},{"content":"JDK Flight Recorder has always captured more than you\u0026rsquo;d think to ask for. Command-line arguments, environment variables, system properties — all useful for figuring out why a JVM is behaving strangely, all written verbatim into the recording file. Which is fine until someone attaches that .jfr file to a support ticket and it turns out --dbpassword was one of the arguments.\nJEP 536 fixes this in JDK 27 by redacting sensitive values inside the JVM, before they\u0026rsquo;re ever written to the recording — not by scrubbing the file afterward and hoping nobody looked at it first.\nThe problem, concretely # This is what used to end up in a recording, untouched:\nexport ACCESS_TOKEN=SECRET_TOKEN java -XX:StartFlightRecording:filename=dump.jfr \\ -Xmx2G \\ -Djavax.net.ssl.keyStorePassword=SECRET_PASSWORD \\ -jar application.jar \\ --dbpassword ANOTHER_SECRET_PASSWORD Three different secrets, three different places they leak from: an environment variable, a system property, and a plain CLI argument. Every one of them lands in jdk.InitialEnvironmentVariable, jdk.InitialSystemProperty, or jdk.JVMInformation events inside the recording, in plain text.\nHow it actually redacts # JFR now recognizes sensitive key/value pairs and arguments as it generates the events, and writes [REDACTED] in place of the value.\nThat distinction matters: a post-hoc scrubber can always miss a field someone adds later, or get skipped if someone reads the raw event stream instead of the final file. Redacting at the source means there\u0026rsquo;s nothing downstream to forget to scrub.\nIt\u0026rsquo;s on by default # You don\u0026rsquo;t have to configure anything to get baseline protection — JFR ships with a default set of glob patterns it already treats as sensitive:\nApplies to Default patterns redact-key (env vars, system properties) *api*key*, *auth*, *client*secret*, *credential*, *jaas*config*, *passphrase*, *passwd*, *password*, *private*key*, *pwd*, *secret*, *token* redact-argument (CLI arguments) Same list, minus *auth* redact-argument deliberately drops *auth* from its default list. *auth* on redact-key catches things like AUTH_TOKEN just fine, but on command-line arguments the same glob would also match --author, --authority, and anything else that merely contains \u0026ldquo;auth\u0026rdquo; — a false positive on ordinary, non-sensitive flags. Worth remembering if you add your own custom patterns: broader globs are more likely to swallow legitimate data the wider they get. Configuring it yourself # Both knobs hang off -XX:FlightRecorderOptions, as sub-options separated by commas — each sub-option\u0026rsquo;s own filters are separated by semicolons:\nexport CONFIDENTIAL=SOME_SECRET java -XX:FlightRecorderOptions:\u0026#39;redact-key=confidential,redact-argument=https://*:*@*\u0026#39; \\ -XX:StartFlightRecording:filename=dump.jfr \\ -Dconfidential=ANOTHER_SECRET \\ -jar application.jar https://john:YET_ANOTHER_SECRET@example.com/login --verbose That single redact-argument=https://*:*@* pattern is doing real work — it catches credentials embedded in URLs, a shape the default list doesn\u0026rsquo;t cover.\nMatching more than one argument. A pattern can span the flag and its value(s) as separate space-separated tokens:\n# matches `--password` followed by exactly one argument java -XX:FlightRecorderOptions:\u0026#39;redact-argument=--password *\u0026#39; ... # matches `--password` followed by exactly two arguments java -XX:FlightRecorderOptions:\u0026#39;redact-argument=--password * *\u0026#39; ... Loading filters from a file — useful once the list gets long enough that jamming it into a JVM flag stops being reasonable — with an @ prefix, one filter per line:\njava \u0026#39;-XX:FlightRecorderOptions:redact-argument=@args.txt,redact-key=@keys.txt\u0026#39; ... Adding to the defaults instead of replacing them with a leading +:\njava -XX:FlightRecorderOptions:\u0026#39;redact-key=+confidential;secret;@keys.txt\u0026#39; ... Turning it off, if you need the old unredacted behavior back for some reason:\njava -XX:FlightRecorderOptions:\u0026#39;redact-argument=none,redact-key=none\u0026#39; ... Checking it actually worked # Two ways to verify redaction is doing what you expect, before you ship a recording anywhere:\n# debug logging while the JVM runs java -Xlog:jfr+redact=debug ... # inspect the events most likely to carry secrets jfr print --events InitialSystemProperty,JVMInformation,StringFlag,InitialEnvironmentVariable dump.jfr The catch # Recordings taken on JDK 27 will look different from recordings taken on earlier versions, by design — anywhere a secret used to show up in plain text, you now get [REDACTED]. If you have existing tooling that parses jdk.InitialEnvironmentVariable or similar events and expects real values, JEP 536 will break it silently rather than loudly, since [REDACTED] is still a valid string in that field. Worth auditing before the upgrade, not after.\nFor the record, the previous unredacted behavior is still one flag away (redact-argument=none,redact-key=none from above) — nothing here is a one-way door.\n","date":"25 September 2026","externalUrl":null,"permalink":"/blog/jfr-in-process-data-redaction-jep-536/","section":"Blog","summary":"Java 27 teaches JFR to redact secrets from recordings before they ever hit disk, instead of trusting whoever opens the dump later not to leak them.","title":"JFR In-Process Data Redaction: JEP 536 in Java 27","type":"blog"},{"content":"","date":"25 September 2026","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/blog/","section":"Blog","summary":"","title":"Blog","type":"blog"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"I\u0026rsquo;m a backend engineer working mostly in Java and Spring Boot, with AWS, Kafka, RabbitMQ and Kinesis on the event-driven side and Elasticsearch and Postgres for storage and search. This is where I write up what I actually run into — JVM internals, migrations with OpenRewrite, production concurrency bugs, and lately Claude Code as part of the day-to-day toolchain — mostly for future-me, and anyone who finds the same problem via search.\n","externalUrl":null,"permalink":"/","section":"codebyice","summary":"","title":"codebyice","type":"page"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]