Durante décadas, o CNPJ (Cadastro Nacional da Pessoa Jurídica) foi composto apenas por números. Isso está prestes a mudar.
A Receita Federal vai adotar o chamado CNPJ alfanumérico, um modelo que passa a permitir letras e números na composição do identificador das empresas.
Para o usuário comum, parece uma mudança pequena. Mas para programadores, DBAs e equipes de TI, o impacto é grande.
Neste guia você vai entender os motivos da mudança, as datas oficiais, os prazos, quanto aumenta a capacidade de registros e, principalmente, como validar o novo CNPJ do jeito certo.
Sumário
O que é o CNPJ alfanumérico?
O novo modelo mantém os 14 caracteres do CNPJ atual, mas passa a aceitar letras e números em parte da composição.
Exemplo fictício:
12.ABC.345/0001-XY
A estrutura básica continua a mesma:
- 12 caracteres de base (que poderão ser letras ou números)
- 2 dígitos verificadores numéricos no final
Ou seja, o CNPJ deixa de ser exclusivamente numérico e passa a ser um identificador alfanumérico.
Por que a Receita Federal está fazendo essa mudança?
O principal motivo é o esgotamento das combinações numéricas.
Com o crescimento do empreendedorismo, a expansão do MEI (Microempreendedor Individual) e o aumento das startups, o número de empresas abertas no Brasil cresceu de forma acelerada.
O modelo numérico tradicional tem uma capacidade limitada. Ao permitir letras, a quantidade de combinações cresce de forma exponencial, garantindo capacidade para as próximas décadas.
Quando entra em vigor o CNPJ alfanumérico?
A mudança já tem cronograma oficial.
A Receita Federal publicou a IN RFB nº 2.229/2024 em 15 de outubro de 2024, com vigência a partir de 25 de outubro de 2024. Já a implementação prática do novo modelo está prevista para julho de 2026.
Segundo a Receita, o CNPJ alfanumérico será atribuído apenas a novas inscrições a partir de julho de 2026.
Um detalhe importante: a transição será progressiva. Nem toda nova inscrição vai receber letras já no primeiro dia, pois a implantação acontecerá de forma gradual.
Os CNPJs antigos vão mudar?
Não.
Esse é um dos pontos mais importantes.
Os CNPJs já existentes continuam exatamente como estão. Não haverá conversão em massa, recadastro obrigatório, troca automática de número nem alteração dos dígitos verificadores atuais.
Na prática, o Brasil vai conviver com dois formatos ao mesmo tempo:
- CNPJ antigo, só com números
- CNPJ novo, com letras e números
Por isso, os sistemas precisam estar preparados para aceitar os dois formatos simultaneamente.
Qual é o prazo real para adaptar os sistemas?
Para software houses, ERPs, emissores fiscais, marketplaces, bancos, fintechs e integradores, o prazo prático é direto:
os sistemas precisam estar prontos antes de julho de 2026.
Resumindo:
- prazo legal da mudança: julho de 2026
- prazo técnico recomendado: o quanto antes
- prazo máximo seguro: antes de julho de 2026
E tem um detalhe extra: no ecossistema fiscal, cada documento eletrônico pode ter seu cronograma técnico próprio de homologação e produção, por meio de Notas Técnicas. Não basta adaptar só o cadastro interno. É preciso acompanhar também NF-e, NFC-e, CT-e, MDF-e e outros documentos.
De quantas combinações para quantas combinações o CNPJ passa?
Aqui vale a explicação simples.
No modelo numérico, a raiz do CNPJ trabalha com cerca de 100 milhões de combinações.
Com a adoção de letras e números, esse universo sobe para quase 3 trilhões de combinações.
Para quem gosta da conta técnica:
- modelo numérico: 10⁸ = 100.000.000
- modelo alfanumérico: 36⁸ = 2.821.109.907.456
Ou seja, salta de 100 milhões para aproximadamente 2,82 trilhões de combinações. É por isso que a mudança faz sentido: evitar o esgotamento da numeração.
O impacto para desenvolvedores é maior do que parece
Muitos sistemas brasileiros foram criados assumindo que o CNPJ sempre seria um número.
Esse é o principal problema.
Durante anos, muitos sistemas armazenaram CNPJs como:
IntegerBigIntNumericLongDouble
Isso já era uma prática inadequada. Agora, passa a ser um erro crítico.
O jeito certo de tratar um CNPJ
O CNPJ nunca deve ser tratado como número.
O correto é tratá-lo como texto.
✅ Correto
VARCHAR(14) CHAR(14) STRING
❌ Incorreto
INT BIGINT FLOAT DOUBLE DECIMAL
O CNPJ é um identificador, não um valor matemático. Ninguém soma, multiplica ou divide um CNPJ. Portanto, ele deve ser armazenado como texto.
Sistemas que precisarão de revisão
A mudança afeta praticamente qualquer software que lide com dados empresariais:
- ERPs
- CRMs
- Sistemas fiscais e contábeis
- Plataformas de e-commerce e marketplaces
- Gateways de pagamento
- Softwares de emissão de notas fiscais
- Sistemas bancários
- APIs de integração
- Sistemas governamentais
- Aplicativos mobile
Mesmo sistemas internos de pequenas empresas podem precisar de atualização.
Bancos de dados: um dos pontos mais críticos
Uma revisão completa deve incluir:
Estrutura das tabelas: campos como cnpj BIGINT ou cnpj NUMERIC(14) precisarão virar cnpj VARCHAR(14).
Índices: alguns índices podem precisar ser recriados.
Procedures e funções: tudo que assume valores numéricos deve ser revisado.
Triggers: as triggers de validação também precisam ser verificadas.
Como validar o novo CNPJ do jeito certo
Esse é o ponto em que muitos sistemas vão quebrar se forem adaptados pela metade.
A validação correta passa por 4 etapas:
1. Normalizar a entrada
Remova a máscara e padronize para maiúsculas.
12.ABC.345/01DE-35 vira 12ABC34501DE35
2. Validar o formato
O formato passa a aceitar 12 posições alfanuméricas e 2 dígitos verificadores numéricos:
(regex)
^[A-Z0-9]{12}[0-9]{2}$
Mas atenção: regex sozinho não valida CNPJ. Ele só verifica se o formato parece correto.
3. Calcular o dígito verificador (DV)
O DV continua usando módulo 11, mas com uma adaptação importante: para cada caractere, use o valor ASCII e subtraia 48
Assim:
0a9continuam valendo0a9A = 65 - 48 = 17B = 66 - 48 = 18C = 67 - 48 = 19- …
Z = 90 - 48 = 42
Agora vamos adotar um CNPJ como exemplo:
AB.C12.345.0001 (os 12 primeiros caracteres)
Pesos do 1º DV:
- A = 17 * 5 = 85
- B = 18 * 4 = 72
- C = 19 * 3 = 57
- 1 = 1 * 2 = 2
- 2 = 2 * 9 = 18
- 3 = 3 * 8 = 24
- 4 = 4 * 7 = 28
- 5 = 5 * 6 = 30
- 0 = 0 * 5 = 0
- 0 = 0 * 4 = 0
- 0 = 0 * 3 = 0
- 1 = 1 * 2 = 2
Somando tudo temos: 318
Divide por 11, temos : 28,90. Pega o 28 e calcula a sobra da divisão: 10
A regra do módulo 11:
- Se for resto 0 ou 1, o dígito é 0
- Se for de 2 a 10, o dígito é 11 – resto.
No nosso caso fica 11 – 10 = 1
Então 1 é o primeiro dígito
Pesos do 2º DV:
- A = 17 * 6 = 102
- B = 18 * 5 = 90
- C = 19 * 4 = 76
- 1 = 1 * 3 = 3
- 2 = 2 * 2 = 4
- 3 = 3 * 9 = 27
- 4 = 4 * 8 = 32
- 5 = 5 * 7 = 35
- 0 = 0 * 6 = 0
- 0 = 0 * 5 = 0
- 0 = 0 * 4 = 0
- 1 = 1 * 3 = 3
- (Primeiro DV) 1 = 1 * 2 = 2
Soma desse item: 374
Divide por 11, ficando 34 com resto zero. Então seguindo a regra fica com o segundo dígito 0.
Então ficamos com o CNPJ assim:
ABC12345/0001-10
4. Comparar com os dois últimos caracteres
Compare os dois dígitos calculados com os dois últimos caracteres do CNPJ informado. Se baterem, o CNPJ é válido.
Funções de validação em várias linguagens
Abaixo você encontra exemplos de validação do CNPJ alfanumérico nas principais linguagens, incluindo opções modernas e legadas.
Ver código em C
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
bool validar_novo_cnpj(const char* cnpj) {
char cnpj_limpo[15];
int idx = 0;
for (int i = 0; cnpj[i] != '\0' && idx < 14; i++) {
if (isalnum((unsigned char)cnpj[i])) {
cnpj_limpo[idx++] = toupper((unsigned char)cnpj[i]);
}
}
cnpj_limpo[idx] = '\0';
if (idx != 14) return false;
if (!isdigit((unsigned char)cnpj_limpo[12]) || !isdigit((unsigned char)cnpj_limpo[13])) return false;
int pesos_dv1[] = {5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
int pesos_dv2[] = {6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
int soma_dv1 = 0;
for (int i = 0; i < 12; i++) {
soma_dv1 += ((int)cnpj_limpo[i] - 48) * pesos_dv1[i];
}
int resto_dv1 = soma_dv1 % 11;
int dv1 = (resto_dv1 < 2) ? 0 : 11 - resto_dv1;
int soma_dv2 = 0;
for (int i = 0; i < 12; i++) {
soma_dv2 += ((int)cnpj_limpo[i] - 48) * pesos_dv2[i];
}
soma_dv2 += dv1 * pesos_dv2[12];
int resto_dv2 = soma_dv2 % 11;
int dv2 = (resto_dv2 < 2) ? 0 : 11 - resto_dv2;
return (cnpj_limpo[12] - '0') == dv1 && (cnpj_limpo[13] - '0') == dv2;
}
Ver código em C#
using System;
using System.Text.RegularExpressions;
public static class CnpjValidator
{
public static bool ValidarNovoCnpj(string cnpj)
{
if (string.IsNullOrWhiteSpace(cnpj)) return false;
string cnpjLimpo = Regex.Replace(cnpj, "[^A-Za-z0-9]", "").ToUpper();
if (cnpjLimpo.Length != 14) return false;
if (!char.IsDigit(cnpjLimpo[12]) || !char.IsDigit(cnpjLimpo[13])) return false;
int[] pesosDv1 = { 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 };
int[] pesosDv2 = { 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 };
int somaDv1 = 0;
for (int i = 0; i < 12; i++)
{
somaDv1 += ((int)cnpjLimpo[i] - 48) * pesosDv1[i];
}
int restoDv1 = somaDv1 % 11;
int dv1 = restoDv1 < 2 ? 0 : 11 - restoDv1;
int somaDv2 = 0;
for (int i = 0; i < 12; i++)
{
somaDv2 += ((int)cnpjLimpo[i] - 48) * pesosDv2[i];
}
somaDv2 += dv1 * pesosDv2[12];
int restoDv2 = somaDv2 % 11;
int dv2 = restoDv2 < 2 ? 0 : 11 - restoDv2;
return (cnpjLimpo[12] - '0') == dv1 && (cnpjLimpo[13] - '0') == dv2;
}
}
Ver código em C++
#pragma once
#include <string>
#include <algorithm>
#include <cctype>
#include <vector>
inline bool validar_novo_cnpj(const std::string& cnpj) {
std::string cnpj_limpo;
for (char c : cnpj) {
if (std::isalnum(static_cast<unsigned char>(c))) {
cnpj_limpo += std::toupper(static_cast<unsigned char>(c));
}
}
if (cnpj_limpo.length() != 14) return false;
if (!std::isdigit(cnpj_limpo[12]) || !std::isdigit(cnpj_limpo[13])) return false;
const int pesos_dv1[] = {5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
const int pesos_dv2[] = {6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
int soma_dv1 = 0;
for (int i = 0; i < 12; ++i) {
soma_dv1 += (static_cast<int>(cnpj_limpo[i]) - 48) * pesos_dv1[i];
}
int resto_dv1 = soma_dv1 % 11;
int dv1 = (resto_dv1 < 2) ? 0 : 11 - resto_dv1;
int soma_dv2 = 0;
for (int i = 0; i < 12; ++i) {
soma_dv2 += (static_cast<int>(cnpj_limpo[i]) - 48) * pesos_dv2[i];
}
soma_dv2 += dv1 * pesos_dv2[12];
int resto_dv2 = soma_dv2 % 11;
int dv2 = (resto_dv2 < 2) ? 0 : 11 - resto_dv2;
return (cnpj_limpo[12] - '0') == dv1 && (cnpj_limpo[13] - '0') == dv2;
}
Ver código em Clipper / Harbour
FUNCTION ValidarNovoCnpj( cCnpj )
LOCAL cCnpjLimpo := ""
LOCAL cChar := ""
LOCAL nI := 0
LOCAL nValor := 0
LOCAL nSomaDv1 := 0, nRestoDv1 := 0, nDv1 := 0
LOCAL nSomaDv2 := 0, nRestoDv2 := 0, nDv2 := 0
LOCAL aPesosDv1 := { 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 }
LOCAL aPesosDv2 := { 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 }
cCnpj := Upper( cCnpj )
FOR nI := 1 TO Len( cCnpj )
cChar := SubStr( cCnpj, nI, 1 )
IF ( cChar >= "0" .AND. cChar <= "9" ) .OR. ( cChar >= "A" .AND. cChar <= "Z" ) cCnpjLimpo += cChar ENDIF NEXT IF Len( cCnpjLimpo ) != 14 RETURN .F. ENDIF IF .NOT. ( SubStr( cCnpjLimpo, 13, 1 ) >= "0" .AND. SubStr( cCnpjLimpo, 13, 1 ) <= "9" ) .OR. ; .NOT. ( SubStr( cCnpjLimpo, 14, 1 ) >= "0" .AND. SubStr( cCnpjLimpo, 14, 1 ) <= "9" )
RETURN .F.
ENDIF
FOR nI := 1 TO 12
nValor := Asc( SubStr( cCnpjLimpo, nI, 1 ) ) - 48
nSomaDv1 += nValor * aPesosDv1[ nI ]
NEXT
nRestoDv1 := nSomaDv1 % 11
nDv1 := If( nRestoDv1 < 2, 0, 11 - nRestoDv1 )
FOR nI := 1 TO 12
nValor := Asc( SubStr( cCnpjLimpo, nI, 1 ) ) - 48
nSomaDv2 += nValor * aPesosDv2[ nI ]
NEXT
nSomaDv2 += nDv1 * aPesosDv2[ 13 ]
nRestoDv2 := nSomaDv2 % 11
nDv2 := If( nRestoDv2 < 2, 0, 11 - nRestoDv2 )
RETURN ( Val( SubStr( cCnpjLimpo, 13, 1 ) ) == nDv1 .AND. ;
Val( SubStr( cCnpjLimpo, 14, 1 ) ) == nDv2 )
Ver código em COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. VALIDACNPJ.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CNPJ-ENTRADA PIC X(20) VALUE "123A5678901200".
01 WS-CNPJ-LIMPO PIC X(14).
01 WS-CNPJ-AUX.
05 WS-CHAR PIC X OCCURS 14 TIMES.
01 WS-VALORES.
05 WS-VAL PIC 9(3) OCCURS 14 TIMES.
01 WS-IND PIC 9(2).
01 WS-IND-LIMPO PIC 9(2) VALUE 1.
01 WS-ASCII PIC 9(3).
01 WS-PESOS-DV1.
05 FILLER PIC 9 VALUE 5. 05 FILLER PIC 9 VALUE 4.
05 FILLER PIC 9 VALUE 3. 05 FILLER PIC 9 VALUE 2.
05 FILLER PIC 9 VALUE 9. 05 FILLER PIC 9 VALUE 8.
05 FILLER PIC 9 VALUE 7. 05 FILLER PIC 9 VALUE 6.
05 FILLER PIC 9 VALUE 5. 05 FILLER PIC 9 VALUE 4.
05 FILLER PIC 9 VALUE 3. 05 FILLER PIC 9 VALUE 2.
01 FILLER REDEFINES WS-PESOS-DV1.
05 WS-P1 PIC 9 OCCURS 12 TIMES.
01 WS-PESOS-DV2.
05 FILLER PIC 9 VALUE 6. 05 FILLER PIC 9 VALUE 5.
05 FILLER PIC 9 VALUE 4. 05 FILLER PIC 9 VALUE 3.
05 FILLER PIC 9 VALUE 2. 05 FILLER PIC 9 VALUE 9.
05 FILLER PIC 9 VALUE 8. 05 FILLER PIC 9 VALUE 7.
05 FILLER PIC 9 VALUE 6. 05 FILLER PIC 9 VALUE 5.
05 FILLER PIC 9 VALUE 4. 05 FILLER PIC 9 VALUE 3.
05 FILLER PIC 9 VALUE 2.
01 FILLER REDEFINES WS-PESOS-DV2.
05 WS-P2 PIC 9 OCCURS 13 TIMES.
01 WS-SOMA PIC 9(6) VALUE 0.
01 WS-RESTO PIC 9(4) VALUE 0.
01 WS-DV1-CALC PIC 9(1) VALUE 0.
01 WS-DV2-CALC PIC 9(1) VALUE 0.
01 WS-VALIDO PIC X(3) VALUE "NAO".
PROCEDURE DIVISION.
MAIN-PROCEDURE.
INSPECT WS-CNPJ-ENTRADA CONVERTING "abcdefghijklmnopqrstuvwxyz"
TO "ABCDEFGHIJKLMNOPQRSTUVWXYZ".
MOVE 1 TO WS-IND-LIMPO.
PERFORM VARYING WS-IND FROM 1 BY 1 UNTIL WS-IND > 20
IF (WS-CNPJ-ENTRADA(WS-IND:1) >= "0" AND <= "9") OR (WS-CNPJ-ENTRADA(WS-IND:1) >= "A" AND <= "Z")
IF WS-IND-LIMPO <= 14 MOVE WS-CNPJ-ENTRADA(WS-IND:1) TO WS-CHAR(WS-IND-LIMPO) ADD 1 TO WS-IND-LIMPO END-IF END-IF END-PERFORM. PERFORM VARYING WS-IND FROM 1 BY 1 UNTIL WS-IND > 14
COMPUTE WS-ASCII = FUNCTION ORD(WS-CHAR(WS-IND))
COMPUTE WS-VAL(WS-IND) = WS-ASCII - 49
END-PERFORM.
MOVE 0 TO WS-SOMA.
PERFORM VARYING WS-IND FROM 1 BY 1 UNTIL WS-IND > 12
COMPUTE WS-SOMA = WS-SOMA + (WS-VAL(WS-IND) * WS-P1(WS-IND))
END-PERFORM.
DIVIDE WS-SOMA BY 11 GIVING WS-SOMA REMAINDER WS-RESTO.
IF WS-RESTO < 2 MOVE 0 TO WS-DV1-CALC ELSE COMPUTE WS-DV1-CALC = 11 - WS-RESTO END-IF. MOVE 0 TO WS-SOMA. PERFORM VARYING WS-IND FROM 1 BY 1 UNTIL WS-IND > 12
COMPUTE WS-SOMA = WS-SOMA + (WS-VAL(WS-IND) * WS-P2(WS-IND))
END-PERFORM.
COMPUTE WS-SOMA = WS-SOMA + (WS-DV1-CALC * WS-P2(13)).
DIVIDE WS-SOMA BY 11 GIVING WS-SOMA REMAINDER WS-RESTO.
IF WS-RESTO < 2
MOVE 0 TO WS-DV2-CALC
ELSE
COMPUTE WS-DV2-CALC = 11 - WS-RESTO
END-IF.
IF WS-VAL(13) = WS-DV1-CALC AND WS-VAL(14) = WS-DV2-CALC
MOVE "SIM" TO WS-VALIDO
END-IF.
DISPLAY "CNPJ VALIDO? " WS-VALIDO.
STOP RUN.
Ver código em Delphi
unit ValidarCnpj;
interface
uses
System.SysUtils, System.RegularExpressions;
function ValidarNovoCnpj(const Cnpj: string): Boolean;
implementation
function ValidarNovoCnpj(const Cnpj: string): Boolean;
var
CnpjLimpo: string;
PesosDv1: array[0..11] of Integer;
PesosDv2: array[0..12] of Integer;
I, SomaDv1, RestoDv1, Dv1, SomaDv2, RestoDv2, Dv2: Integer;
begin
Result := False;
CnpjLimpo := TRegEx.Replace(Cnpj, '[^A-Za-z0-9]', '').ToUpper;
if Length(CnpjLimpo) <> 14 then Exit;
if not (CnpjLimpo[13].IsDigit and CnpjLimpo[14].IsDigit) then Exit;
PesosDv1[0] := 5; PesosDv1[1] := 4; PesosDv1[2] := 3; PesosDv1[3] := 2;
PesosDv1[4] := 9; PesosDv1[5] := 8; PesosDv1[6] := 7; PesosDv1[7] := 6;
PesosDv1[8] := 5; PesosDv1[9] := 4; PesosDv1[10] := 3; PesosDv1[11] := 2;
PesosDv2[0] := 6; PesosDv2[1] := 5; PesosDv2[2] := 4; PesosDv2[3] := 3;
PesosDv2[4] := 2; PesosDv2[5] := 9; PesosDv2[6] := 8; PesosDv2[7] := 7;
PesosDv2[8] := 6; PesosDv2[9] := 5; PesosDv2[10] := 4; PesosDv2[11] := 3;
PesosDv2[12] := 2;
SomaDv1 := 0;
for I := 0 to 11 do
SomaDv1 := SomaDv1 + (Ord(CnpjLimpo[I + 1]) - 48) * PesosDv1[I];
RestoDv1 := SomaDv1 mod 11;
if RestoDv1 < 2 then Dv1 := 0 else Dv1 := 11 - RestoDv1;
SomaDv2 := 0;
for I := 0 to 11 do
SomaDv2 := SomaDv2 + (Ord(CnpjLimpo[I + 1]) - 48) * PesosDv2[I];
SomaDv2 := SomaDv2 + (Dv1 * PesosDv2[12]);
RestoDv2 := SomaDv2 mod 11;
if RestoDv2 < 2 then Dv2 := 0 else Dv2 := 11 - RestoDv2;
Result := (Ord(CnpjLimpo[13]) - 48 = Dv1) and (Ord(CnpjLimpo[14]) - 48 = Dv2);
end;
end.
Ver código em Elixir
defmodule CnpjValidator do
def validar_novo_cnpj(cnpj) when is_binary(cnpj) do
cnpj_limpo = String.replace(cnpj, ~r/[^A-Za-z0-9]/, "") |> String.upcase()
if String.length(cnpj_limpo) == 14 and String.match?(String.slice(cnpj_limpo, 12..13), ~r/^\d{2}$/) do
chars = String.to_charlist(cnpj_limpo)
pesos_dv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
pesos_dv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
soma_dv1 = Enum.zip(Enum.take(chars, 12), pesos_dv1)
|> Enum.reduce(0, fn {char, peso}, acc -> acc + (char - 48) * peso end)
resto_dv1 = rem(soma_dv1, 11)
dv1 = if resto_dv1 < 2, do: 0, else: 11 - resto_dv1 soma_dv2 = Enum.zip(Enum.take(chars, 12), pesos_dv2) |> Enum.reduce(0, fn {char, peso}, acc -> acc + (char - 48) * peso end)
soma_dv2 = soma_dv2 + (dv1 * Enum.at(pesos_dv2, 12))
resto_dv2 = rem(soma_dv2, 11)
dv2 = if resto_dv2 < 2, do: 0, else: 11 - resto_dv2
Enum.at(chars, 12) - 48 == dv1 and Enum.at(chars, 13) - 48 == dv2
else
false
end
end
end
Ver código em Go
package cnpj
import (
"regexp"
"strings"
)
func ValidarNovoCnpj(cnpj string) bool {
reg := regexp.MustCompile("[^A-Za-z0-9]")
cnpjLimpo := strings.ToUpper(reg.ReplaceAllString(cnpj, ""))
if len(cnpjLimpo) != 14 {
return false
}
if cnpjLimpo[12] < '0' || cnpjLimpo[12] > '9' || cnpjLimpo[13] < '0' || cnpjLimpo[13] > '9' {
return false
}
pesosDv1 := []int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
pesosDv2 := []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
somaDv1 := 0
for i := 0; i < 12; i++ { somaDv1 += (int(cnpjLimpo[i]) - 48) * pesosDv1[i] } restoDv1 := somaDv1 % 11 dv1 := 0 if restoDv1 >= 2 {
dv1 = 11 - restoDv1
}
somaDv2 := 0
for i := 0; i < 12; i++ { somaDv2 += (int(cnpjLimpo[i]) - 48) * pesosDv2[i] } somaDv2 += dv1 * pesosDv2[12] restoDv2 := somaDv2 % 11 dv2 := 0 if restoDv2 >= 2 {
dv2 = 11 - restoDv2
}
return int(cnpjLimpo[12]-'0') == dv1 && int(cnpjLimpo[13]-'0') == dv2
}
Ver código em Java
public class CnpjValidator {
public static boolean validarNovoCnpj(String cnpj) {
if (cnpj == null) return false;
String cnpjLimpo = cnpj.replaceAll("[^A-Za-z0-9]", "").toUpperCase();
if (cnpjLimpo.length() != 14) return false;
if (!cnpjLimpo.substring(12).matches("\\d{2}")) return false;
int[] pesosDv1 = {5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
int[] pesosDv2 = {6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
int somaDv1 = 0;
for (int i = 0; i < 12; i++) {
somaDv1 += ((int) cnpjLimpo.charAt(i) - 48) * pesosDv1[i];
}
int restoDv1 = somaDv1 % 11;
int dv1 = restoDv1 < 2 ? 0 : 11 - restoDv1;
int somaDv2 = 0;
for (int i = 0; i < 12; i++) {
somaDv2 += ((int) cnpjLimpo.charAt(i) - 48) * pesosDv2[i];
}
somaDv2 += dv1 * pesosDv2[12];
int restoDv2 = somaDv2 % 11;
int dv2 = restoDv2 < 2 ? 0 : 11 - restoDv2;
return Character.getNumericValue(cnpjLimpo.charAt(12)) == dv1 &&
Character.getNumericValue(cnpjLimpo.charAt(13)) == dv2;
}
}
Ver código em JavaScript
function validarNovoCnpj(cnpj) {
if (typeof cnpj !== 'string') return false;
const cnpjLimpo = cnpj.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
if (cnpjLimpo.length !== 14) return false;
if (!/^\d{2}$/.test(cnpjLimpo.slice(-2))) return false;
const pesosDv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const pesosDv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let somaDv1 = 0;
for (let i = 0; i < 12; i++) {
somaDv1 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv1[i];
}
const restoDv1 = somaDv1 % 11;
const dv1 = restoDv1 < 2 ? 0 : 11 - restoDv1;
let somaDv2 = 0;
for (let i = 0; i < 12; i++) {
somaDv2 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv2[i];
}
somaDv2 += dv1 * pesosDv2[12];
const restoDv2 = somaDv2 % 11;
const dv2 = restoDv2 < 2 ? 0 : 11 - restoDv2;
return parseInt(cnpjLimpo[12], 10) === dv1 && parseInt(cnpjLimpo[13], 10) === dv2;
}
Ver código em Kotlin
object CnpjValidator {
fun validarNovoCnpj(cnpj: String): Boolean {
val cnpjLimpo = cnpj.replace(Regex("[^A-Za-z0-9]"), "").uppercase()
if (cnpjLimpo.length != 14) return false;
if (!cnpjLimpo.takeLast(2).all { it.isDigit() }) return false
val pesosDv1 = intArrayOf(5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
val pesosDv2 = intArrayOf(6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
var somaDv1 = 0
for (i in 0 until 12) {
somaDv1 += (cnpjLimpo[i].code - 48) * pesosDv1[i]
}
val restoDv1 = somaDv1 % 11
val dv1 = if (restoDv1 < 2) 0 else 11 - restoDv1
var somaDv2 = 0
for (i in 0 until 12) {
somaDv2 += (cnpjLimpo[i].code - 48) * pesosDv2[i]
}
somaDv2 += dv1 * pesosDv2[12]
val restoDv2 = somaDv2 % 11
val dv2 = if (restoDv2 < 2) 0 else 11 - restoDv2
return (cnpjLimpo[12] - '0') == dv1 && (cnpjLimpo[13] - '0') == dv2
}
}
Ver código em Node.js
function validarNovoCnpj(cnpj) {
if (typeof cnpj !== 'string') return false;
const cnpjLimpo = cnpj.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
if (cnpjLimpo.length !== 14) return false;
if (!/^\d{2}$/.test(cnpjLimpo.slice(-2))) return false;
const pesosDv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const pesosDv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let somaDv1 = 0;
for (let i = 0; i < 12; i++) {
somaDv1 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv1[i];
}
const restoDv1 = somaDv1 % 11;
const dv1 = restoDv1 < 2 ? 0 : 11 - restoDv1;
let somaDv2 = 0;
for (let i = 0; i < 12; i++) {
somaDv2 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv2[i];
}
somaDv2 += dv1 * pesosDv2[12];
const restoDv2 = somaDv2 % 11;
const dv2 = restoDv2 < 2 ? 0 : 11 - restoDv2;
return parseInt(cnpjLimpo[12], 10) === dv1 && parseInt(cnpjLimpo[13], 10) === dv2;
}
module.exports = { validarNovoCnpj };
Ver código em PHP
<?php
function validarNovoCnpj(string $cnpj): bool {
$cnpjLimpo = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $cnpj));
if (strlen($cnpjLimpo) !== 14) return false;
if (!ctype_digit(substr($cnpjLimpo, -2))) return false;
$pesosDv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
$pesosDv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
$somaDv1 = 0;
for ($i = 0; $i < 12; $i++) {
$somaDv1 += (ord($cnpjLimpo[$i]) - 48) * $pesosDv1[$i];
}
$restoDv1 = $somaDv1 % 11;
$dv1 = $restoDv1 < 2 ? 0 : 11 - $restoDv1;
$somaDv2 = 0;
for ($i = 0; $i < 12; $i++) {
$somaDv2 += (ord($cnpjLimpo[$i]) - 48) * $pesosDv2[$i];
}
$somaDv2 += $dv1 * $pesosDv2[12];
$restoDv2 = $somaDv2 % 11;
$dv2 = $restoDv2 < 2 ? 0 : 11 - $restoDv2;
return (int)$cnpjLimpo[12] === $dv1 && (int)$cnpjLimpo[13] === $dv2;
}
Ver código em Python
import re
def validar_novo_cnpj(cnpj: str) -> bool:
cnpj_limpo = re.sub(r'[^A-Za-z0-9]', '', cnpj).upper()
if len(cnpj_limpo) != 14 or not cnpj_limpo[-2:].isdigit():
return False
pesos_dv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
pesos_dv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
soma_dv1 = sum((ord(cnpj_limpo[i]) - 48) * pesos_dv1[i] for i in range(12))
resto_dv1 = soma_dv1 % 11
dv1 = 0 if resto_dv1 < 2 else 11 - resto_dv1
soma_dv2 = sum((ord(cnpj_limpo[i]) - 48) * pesos_dv2[i] for i in range(12))
soma_dv2 += dv1 * pesos_dv2[12]
resto_dv2 = soma_dv2 % 11
dv2 = 0 if resto_dv2 < 2 else 11 - resto_dv2
return int(cnpj_limpo[12]) == dv1 and int(cnpj_limpo[13]) == dv2
Ver código em Ruby
def validar_novo_cnpj(cnpj)
cnpj_limpo = cnpj.gsub(/[^A-Za-z0-9]/, '').upcase
return false if cnpj_limpo.length != 14
return false unless cnpj_limpo[-2..-1].match?(/\A\d+\z/)
pesos_dv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
pesos_dv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
soma_dv1 = 0
12.times { |i| soma_dv1 += (cnpj_limpo[i].ord - 48) * pesos_dv1[i] }
resto_dv1 = soma_dv1 % 11
dv1 = resto_dv1 < 2 ? 0 : 11 - resto_dv1
soma_dv2 = 0
12.times { |i| soma_dv2 += (cnpj_limpo[i].ord - 48) * pesos_dv2[i] }
soma_dv2 += dv1 * pesos_dv2[12]
resto_dv2 = soma_dv2 % 11
dv2 = resto_dv2 < 2 ? 0 : 11 - resto_dv2
cnpj_limpo[12].to_i == dv1 && cnpj_limpo[13].to_i == dv2
end
Ver código em Rust
pub fn validar_novo_cnpj(cnpj: &str) -> bool {
let cnpj_limpo: String = cnpj
.chars()
.filter(|c| c.is_alphanumeric())
.collect::()
.to_uppercase();
if cnpj_limpo.len() != 14 {
return false;
}
let chars: Vec = cnpj_limpo.chars().collect();
if !chars[12].is_ascii_digit() || !chars[13].is_ascii_digit() {
return false;
}
let pesos_dv1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let pesos_dv2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let mut soma_dv1 = 0;
for i in 0..12 {
soma_dv1 += ((chars[i] as i32) - 48) * pesos_dv1[i];
}
let resto_dv1 = soma_dv1 % 11;
let dv1 = if resto_dv1 < 2 { 0 } else { 11 - resto_dv1 };
let mut soma_dv2 = 0;
for i in 0..12 {
soma_dv2 += ((chars[i] as i32) - 48) * pesos_dv2[i];
}
soma_dv2 += dv1 * pesos_dv2[12];
let resto_dv2 = soma_dv2 % 11;
let dv2 = if resto_dv2 < 2 { 0 } else { 11 - resto_dv2 };
(chars[12] as i32 - 48) == dv1 && (chars[13] as i32 - 48) == dv2
}
Ver código em TypeScript
export function validarNovoCnpj(cnpj: string): boolean {
const cnpjLimpo: string = cnpj.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
if (cnpjLimpo.length !== 14) return false;
if (!/^\d{2}$/.test(cnpjLimpo.slice(-2))) return false;
const pesosDv1: number[] = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const pesosDv2: number[] = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let somaDv1 = 0;
for (let i = 0; i < 12; i++) {
somaDv1 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv1[i];
}
const restoDv1: number = somaDv1 % 11;
const dv1: number = restoDv1 < 2 ? 0 : 11 - restoDv1;
let somaDv2 = 0;
for (let i = 0; i < 12; i++) {
somaDv2 += (cnpjLimpo.charCodeAt(i) - 48) * pesosDv2[i];
}
somaDv2 += dv1 * pesosDv2[12];
const restoDv2: number = somaDv2 % 11;
const dv2: number = restoDv2 < 2 ? 0 : 11 - restoDv2;
return parseInt(cnpjLimpo[12], 10) === dv1 && parseInt(cnpjLimpo[13], 10) === dv2;
}
Ver código em VB6 / VBA
Public Function ValidarNovoCnpj(ByVal Cnpj As String) As Boolean
Dim CnpjLimpo As String
Dim CharCode As Integer
Dim I As Integer, Valor As Integer
Dim SomaDv1 As Long, RestoDv1 As Integer, Dv1 As Integer
Dim SomaDv2 As Long, RestoDv2 As Integer, Dv2 As Integer
Dim PesosDv1() As Variant
Dim PesosDv2() As Variant
PesosDv1 = Array(5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
PesosDv2 = Array(6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
CnpjLimpo = ""
Cnpj = UCase(Cnpj)
For I = 1 To Len(Cnpj)
Dim C As String
C = Mid(Cnpj, I, 1)
If (C >= "0" And C <= "9") Or (C >= "A" And C <= "Z") Then
CnpjLimpo = CnpjLimpo & C
End If
Next I
If Len(CnpjLimpo) <> 14 Then
ValidarNovoCnpj = False
Exit Function
End If
If Not (Mid(CnpjLimpo, 13, 1) >= "0" And Mid(CnpjLimpo, 13, 1) <= "9") Or _ Not (Mid(CnpjLimpo, 14, 1) >= "0" And Mid(CnpjLimpo, 14, 1) <= "9") Then
ValidarNovoCnpj = False
Exit Function
End If
SomaDv1 = 0
For I = 0 To 11
CharCode = Asc(Mid(CnpjLimpo, I + 1, 1))
Valor = CharCode - 48
SomaDv1 = SomaDv1 + (Valor * PesosDv1(I))
Next I
RestoDv1 = SomaDv1 Mod 11
If RestoDv1 < 2 Then Dv1 = 0 Else Dv1 = 11 - RestoDv1
SomaDv2 = 0
For I = 0 To 11
CharCode = Asc(Mid(CnpjLimpo, I + 1, 1))
Valor = CharCode - 48
SomaDv2 = SomaDv2 + (Valor * PesosDv2(I))
Next I
SomaDv2 = SomaDv2 + (Dv1 * PesosDv2(12))
RestoDv2 = SomaDv2 Mod 11
If RestoDv2 < 2 Then Dv2 = 0 Else Dv2 = 11 - RestoDv2
Dim Dv1Informado As Integer
Dim Dv2Informado As Integer
Dv1Informado = Val(Mid(CnpjLimpo, 13, 1))
Dv2Informado = Val(Mid(CnpjLimpo, 14, 1))
ValidarNovoCnpj = (Dv1 = Dv1Informado) And (Dv2 = Dv2Informado)
End Function
[INSIRA AQUI AS FUNÇÕES DE VALIDAÇÃO]
Ruby · Python · PHP · Java · JavaScript · Rust · Go · TypeScript · Elixir · COBOL e outras.
O jeito certo e o jeito errado de validar
✅ Jeito certo:
- tratar CNPJ como string
- aceitar números e letras
- remover máscara antes do cálculo
- usar módulo 11 com ASCII − 48
- manter compatibilidade com CNPJ antigo e novo
❌ Jeito errado:
- usar
parseInt,Number,BIGINTouLong - validar com regex antiga como
^\d{14}$ - assumir que CNPJ sempre terá só números
- ordenar ou comparar CNPJ como número
- confiar só na máscara visual
APIs precisarão ser revisadas
Muitas APIs ainda usam contratos como:
(json)
{ "cnpj": 12345678000199 }
Esse modelo deve ser evitado. O correto é tratar o CNPJ como string:
(json)
{ "cnpj": "12345678000199" }
Ou, futuramente:
(json)
{ "cnpj": "12ABC3450001XY" }
A mudança impacta APIs REST, GraphQL, SOAP, microsserviços, filas de mensagens e integrações com parceiros.
O erro mais perigoso: converter automaticamente
Muitos sistemas fazem conversões automáticas, como:
Long.parseLong(cnpj) Number(cnpj) int(cnpj)
Quando as letras começarem a aparecer, essas rotinas vão gerar erros. Por isso, é fundamental localizar todas as conversões numéricas relacionadas ao CNPJ.
Ponto importante: a adaptação precisa ser de ponta a ponta
O maior erro será fazer uma adaptação cosmética: aceitar letra no front-end, mas continuar gravando como número no banco, ou manter a validação antiga no back-end, ou quebrar integrações porque a API ainda espera inteiro.
Para funcionar de verdade, a adaptação precisa cobrir todas as camadas:
- front-end
- back-end
- banco de dados
- APIs
- mensageria
- relatórios e BI
- fiscal
- integrações externas
Sistemas legados merecem atenção especial
Os sistemas mais antigos provavelmente serão os mais afetados. É comum encontrar comparações numéricas, ordenações numéricas, máscaras fixas, campos limitados, exportações para planilhas e integrações via arquivos TXT.
Uma auditoria completa do código é altamente recomendada.
O impacto nos relatórios e no BI
Ferramentas de Business Intelligence também precisarão de ajustes, pois muitos painéis usam o CNPJ como chave de relacionamento. Quando o campo muda de numérico para textual, alguns relacionamentos podem falhar.
Ferramentas que merecem revisão: Power BI, Tableau, Qlik, Looker e Metabase.
O impacto para startups e empresas de tecnologia
As empresas que se prepararem cedo terão vantagem competitiva, porque:
- evitam retrabalho futuro
- reduzem riscos operacionais
- mantêm conformidade legal
- evitam falhas em integrações
Para startups, é uma boa oportunidade de modernizar sistemas que já deveriam tratar identificadores como texto.
O que muda para o empreendedor brasileiro?
Embora a alteração seja técnica, seus efeitos alcançam o ambiente de negócios. Os benefícios incluem:
- maior capacidade de registros empresariais
- sustentação do crescimento econômico
- suporte ao aumento do empreendedorismo
- menor risco de esgotamento da numeração
- continuidade dos processos de formalização
Em resumo, a mudança ajuda a garantir que o sistema de identificação empresarial acompanhe a evolução da economia digital brasileira.
Checklist para Programadores
- ✅ Tratar CNPJ como texto
- ✅ Revisar banco de dados
- ✅ Atualizar APIs
- ✅ Verificar validações
- ✅ Implementar o novo cálculo de DV (módulo 11 com ASCII − 48)
- ✅ Aceitar CNPJ antigo e novo ao mesmo tempo
- ✅ Revisar integrações externas
- ✅ Atualizar documentação
- ✅ Revisar ETLs e processos de BI
- ✅ Testar importações e exportações
- ✅ Revisar sistemas legados
- ✅ Acompanhar as publicações oficiais da Receita Federal
Conclusão
A chegada do CNPJ alfanumérico é uma das maiores mudanças estruturais em sistemas corporativos brasileiros dos últimos anos.
Para o usuário final, parece pouco. Para o desenvolvedor, exige atenção imediata.
A principal lição é simples: CNPJ não é número. É um identificador.
Sistemas que já seguem essa prática terão poucas adaptações. Já aqueles que dependem de conversões numéricas, validações rígidas ou modelagens antigas poderão enfrentar desafios consideráveis.
O melhor momento para começar a revisão é agora, antes que os primeiros CNPJs alfanuméricos comecem a circular em larga escala.
Perguntas Frequentes (FAQ)
1. Quando o CNPJ alfanumérico entra em vigor?
A implementação prática está prevista para julho de 2026, quando novas inscrições poderão receber letras e números.
2. Os CNPJs antigos vão mudar?
Não. Os CNPJs atuais continuam iguais. O Brasil terá os dois formatos convivendo ao mesmo tempo.
3. Quantas combinações o novo CNPJ permite?
A capacidade salta de cerca de 100 milhões para quase 3 trilhões de combinações.
4. Como validar o novo CNPJ?
Trate como texto, valide o formato, calcule o DV com módulo 11 usando ASCII − 48 e compare com os dois últimos caracteres.
5. O que mais impacta os programadores?
Parar de tratar o CNPJ como número. Ele deve ser string em todas as camadas: banco, back-end, API e front-end.
Fontes
- Receita Federal — CNPJ Alfanumérico
- Santander — CNPJ alfanumérico
- Serasa Experian — CNPJ Alfanumérico
- O Globo — quase 3 trilhões de combinações










