← Back to blog

Porting ZXing to Go with Claude Code

AI-generated image of a robotic engineer building a barcode scanning software library

See the final project here: zxinggo.

In ~23 prompts, Claude Code ported the core of ZXing to pure Go (including detection, decoding, and encoding for all barcode formats) with surprisingly little manual intervention.

This post documents what worked, what broke, and what this says about the future of software engineering and knowledge work.


Why ZXing?

During my time building identity verification at Berbix, I became mildly obsessed with barcodes.

Prior to our acquisition in 2023 by Socure, the Berbix product allowed our customers to instantly capture, extract and validate the data coming from government-issued photo IDs. This included capturing the human-readable portion of those IDs. However, even with the advances in OCR technology over the last several years, it remains an imperfect technology in the real world, where blurry photos or imperfect subject documents can generate errors.

There was a solution to this built into most of the IDs we captured: all US driver licenses and ID cards contain a PDF417 barcode. That barcode includes the same data printed on the front of the card (plus additional fields and error correction) making it a far more reliable data source than OCR.

The gold standard open-source implementation of barcode scanning is ZXing. Originally developed at Google, it supports QR codes, PDF417, Data Matrix, Aztec, and a range of 1D formats. For years, it was a core component of our production stack.

But ZXing has constraints:

  • The project is now in maintenance mode (despite heroic efforts from the maintainers, including srowen in particular).
  • Several TODOs and long-standing improvements will likely never be completed.
  • Some implementations (and particularly parts of PDF417) contain subtle edge-case issues.
  • Most non-Java ports are incomplete or also effectively unmaintained.
  • Existing Go ports (notably gozxing) do not implement PDF417 or several other formats.

If you’re building in Go and need robust PDF417 support, there isn’t a production-grade option. So I decided to see whether Claude Code could build one.


The Experiment

Inspired by Anthropic’s post, Building a C compiler with a team of parallel Claudes and Christopher Ehrlich’s Codex port of SimCity I wanted to test something concrete:

Could Claude port a large, mature, bit-manipulation-heavy Java library to idiomatic, pure Go?

ZXing is not trivial code:

  • Heavy bit math
  • Error correction logic
  • Finite state machines
  • Image scanning heuristics
  • Large constant tables
  • Cross-referenced decoder/encoder logic

I cloned the ZXing repository and gave Claude a simple starting instruction:

I’d like to create a pure Go port of the zxing library which I have cloned in the zxing directory.

Claude immediately switched into planning mode and proposed incremental format support (QR, 1D, etc.). I instructed it to include PDF417 from the start and to implement encoder, detector, and decoder together. Critically, this decision allowed Claude to have a built-in correctness harness. It could generate barcodes, scan them, and validate correct decoding all in a single round trip per barcode format.


What Actually Happened

Reviewing my shell history:

  • I issued 23 high-level prompts.
  • Claude executed hundreds of subordinate commands.
  • It wrote micro-scripts to inspect behavior.
  • It ran builds, go vet, tests, and validation loops autonomously.
  • It spawned subagents to isolate failing logic.

My job was mostly to make sure it wasn’t going completely off the rails or failing in scenarios that it should not be failing.

In addition to my 23 high-level prompts, I issued another 19 prompts to remind Claude to commit code, update READMEs, run benchmarks and write summaries.

Where Claude Succeeded

1. Structural Translation

Large-scale architectural translation (packages, interfaces, separation of concerns) was handled extremely well. My intuition has been that high level structure is the area where humans require the most oversight for AI coding tools, but this is less true when porting an existing project. It was able to clearly identify opportunities to use idiomatic structures rather than a pure port (e.g. interfaces instead of inheritance, errors instead of exceptions).

2. Autonomous Debugging

When test cases failed, Claude would:

  • Write investigation scripts
  • Isolate mismatched bit patterns
  • Compare behavior against the Java version
  • Patch and re-run tests

It effectively treated the Java version as an oracle, using it extensively when looking for behavioral discrepancies in failing test cases.

3. Subagent Orchestration

Claude was particularly strong at spinning up focused agents to:

  • Subdivide the work of detecting, encoding and decoding for a particular format
  • Analyze failing test cases, including generating test scripts

The primary coordinator simply observed the subagents like a manager of a software team. In some cases, the subagent would take a significant amount of time and the coordinator would sleep itself to give the subagent more time. This was particularly true when the subagent was mid-refactor and broke the build. This is not unlike human software development teams, where broken builds can cause downstream ripple effects across the entire engineering team.

As a random aside, during my first year as a professional software engineer, I managed to break the build at Google through a bad merge. Unfortunately, the project I was working on was included by every major Google property, so within seconds of merging I got dozens of messages from very senior engineers demanding a revert. I wouldn’t be surprised to see agent teams starting to exhibit this behavior among themselves in the near future (if it’s not already happening).


Where It Struggled

Logical Translation Errors

Most failures were semantic rather than structural.

Examples included:

  • Off-by-one (and sometimes off-by-several) errors array indexing translation.
  • Incorrect loop post-decrement behavior.
  • Subtle differences between Java’s unsigned right shift (>>>) and Go’s shift semantics.
  • Bit masking mistakes in error correction routines.

These were real bugs — the kind that would be inevitable when a human ports low-level code. The difference is that Claude could usually find and fix them itself thanks to its own validation loop.


Large Constant Blocks

One particularly painful file was PDF417Common.java. It contains large constant arrays used throughout the decoding process. Claude repeatedly introduced minor transcription errors when manually translating these blocks.

To address this, I instructed it to write a script to extract and transform the constants mechanically instead of retyping them. Once scripted, the issue disappeared. Understanding model limitations (especially around large constant blocks and context windows) is still part of the human’s job. Without intervention, Claude likely would have continued iterating blindly on transcription errors.


Performance Improvements

Halfway through the porting process, once the detection of PDF417s was stable, I indirectly asked Claude to implement one of ZXing’s long-standing TODOs:

Continue scanning after a false positive start sequence in PDF417 detection.

This was a serious thorn in my side during my time at Berbix. Often times in natural photos, the system can “detect” the start sequence of a PDF417 barcode. What this means is finding a pattern of dark and light with widths that happen to approximately match the pattern of black and white in the PDF417 start sequence. If it has one of these false positives, it would stop detection early and fail to detect otherwise clear barcodes.

Claude was able to understand the assignment and implement the fix once I gave it an image where the behavior was demonstrated. It did so by creating scripts to output the binarization and walking through the code with debug output, exactly how a human might do it. In fact, that’s exactly how I did it when I addressed this bug during my days at Berbix. The only difference is that it took me several days of digging and understanding barcode semantics, while it took Claude Code less than 10 minutes.

I’ve always thought of myself as a pretty good engineer. Given a problem to solve, I could usually figure it out quickly, perhaps not as fast as the best engineers I’ve ever worked with, but certainly faster than average. But Claude has humbled me significantly. And in the process, actually fixed a longstanding edge case issue and addressed a TODO that has been in place for over half a decade.


Observations

1. Large Ports Are Now Feasible

Porting a major library to a new language is no longer a multi-month manual endeavor if the right conditions are true:

  • There is a strong validation harness.
  • The source implementation is correct.
  • Success/failure can be programmatically evaluated.

ZXing satisfies all three. Without a test oracle, this would have likely been a very different story.


2. Tests Are the Real Leverage

Claude was dramatically more capable because:

  • Encode/decode round-trips were measurable.
  • Image tests produced binary pass/fail signals.
  • The Java implementation could serve as behavioral ground truth.

The future of LLM-assisted engineering belongs to codebases with strong validation infrastructure.


3. Models Are Not Yet Bit-Perfect

High-fidelity copying of large, constant-heavy blocks or low-level bitwise math is still error-prone. It took several round trips for Claude to find and address issues in the initial port, and performed full rewrites of certain components several times over the course of the project.


4. The World Is Shifting

For context, my first child was born in September 2025. I stepped away from technical projects for several months to focus on being a present dad and partner. Returning in early 2026, the improvement in model capability and tooling autonomy shocked and impressed me.

It is increasingly difficult to justify writing large volumes of mechanical code by hand when:

  • The model can translate structure.
  • The model can generate tests.
  • The model can debug itself.
  • The model can orchestrate subagents.

The bottleneck is no longer typing code.

The bottleneck is now:

  • Defining intent.
  • Constructing validation harnesses.
  • Exercising taste and architectural judgment.
  • Knowing what should exist.

At this point, it’s hard to see a world where software engineers are tasked with writing code the old-fashioned way ever again outside of very limited domains where the training data is insufficient to write the code with LLMs or their successors.

It seems clear that the ground beneath our feet is shifting in an undeniable way. The future of writing software becomes a matter of vision, taste and human ingenuity. While today this a clear demonstration of the capability of code writing tools, it’s easy to see how this type of feedback loop could extend to any domain where there are verifiably right answers. It’s an exciting and scary time for anyone whose livelihood depends on sitting in front of a computer.


Final Result

The final repository is hosted on GitHub. The README includes support data, feature descriptions, known issues and basic benchmarks. See it here:

https://github.com/ericlevine/zxinggo

If you’re building in Go and need robust barcode support, it may save you from running a Java sidecar. Let me know if you have any feedback or contributions.