← All writing

Changing an Identifier Format Without Breaking Integrations

A practical approach to supporting alphanumeric CNPJ values while preserving existing numeric identifiers.

  • API Compatibility
  • Validation
  • Automated Testing

An identifier format change can look like a small validation update. But identifiers pass through APIs, applications, and integrations. If one component starts accepting a new format while another still assumes the old one, valid customers can run into failures far from the code that introduced the change.

I worked on adding support for Brazil’s alphanumeric CNPJ format while preserving compatibility with existing numeric identifiers. The implementation included check-digit validation and automated coverage for valid, invalid, lowercase, malformed, and edge-case inputs.

Treat compatibility as a requirement

When an established identifier gains a new format, existing values still matter. It is not enough to make the new examples pass. The change also needs to preserve the behavior callers already rely on for numeric identifiers.

That means writing down the accepted input space for both formats and being precise about what “compatible” means. Which values are accepted? Which are rejected? How should malformed input behave? Those answers should be consistent anywhere the identifier is validated.

The exact checks depend on the identifier’s specification and the systems that consume it. A regular expression can recognize a shape, but it cannot replace semantic validation such as check digits.

Make edge cases deliberate

The automated test coverage for this change included valid and invalid values, lowercase input, malformed input, and other edge cases. That list is useful because format migrations often fail in the boundaries: an unexpected character, mixed case, or a value that has the right length but is still invalid.

For each case, decide whether the system should accept it, normalize it, or return a validation error. Then encode that decision in tests. Lowercase input is a good example: the important thing is not to make an assumption, but to define the expected behavior and keep it consistent.

Tests should also keep the old format visible. When new-format tests are added, the established numeric cases should continue to pass. Otherwise, a successful migration can quietly become a breaking change for existing clients.

Follow the identifier across boundaries

Validation rarely lives in one place. A value may be checked at an API boundary and then passed to other services or stored for later use. Before changing accepted formats, identify the places where the identifier is created, validated, compared, and consumed. Each boundary should agree on the same rules.

This is especially important for identifiers because they are easy to mistake for numbers. If an identifier is treated as a numeric quantity, formatting details can be lost. Model it as an identifier and preserve its exact representation according to the system’s contract.

The lesson

Supporting alphanumeric CNPJs was a focused implementation, but it reinforced a broader engineering habit: make compatibility explicit. Keep old inputs working, validate new values against their actual rules, define behavior for awkward inputs, and automate those expectations.

Those steps make format changes easier to review and safer to integrate. They also give future maintainers a clear answer when someone asks whether a value that looks unusual is supposed to work.