Skip to content

Classify NIF by entity type #1

Description

@hstefanov1

Summary

Allow the application to determine the type of entity associated with a Portuguese Tax Identification Number (NIF).

Problem statement

Currently, it is only possible to verify whether a NIF is structurally valid. There is no way to determine what kind of entity it represents, such as an individual taxpayer, a company, a public body, or a fund, which limits the ability to apply entity-specific business rules downstream.

Proposed solution

Implement classification of NIFs by entity type, based on the ranges defined by Portuguese tax legislation (Decreto-Lei n.º 14/2013).

NIF Types

The following entity types must be represented as a NifType enum:

Type Description
INDIVIDUAL Individual taxpayer, resident or non-resident (prefixes: 1, 2, 3)
NON_RESIDENT_INDIVIDUAL_WITHHOLDING Non-resident individual with income subject to definitive withholding tax (prefix: 45)
LEGAL_ENTITY Legal entity registered with the RNPC (prefix: 5)
PUBLIC_ADMINISTRATION Central, regional, or local public administration body (prefix: 6)
UNDIVIDED_ESTATE Undivided estate — herança indivisa (prefixes: 70, 74, 75)
NON_RESIDENT_LEGAL_ENTITY_WITHHOLDING Non-resident legal entity with income subject to definitive withholding tax (prefix: 71)
INVESTMENT_AND_PENSION_FUNDS Investment funds and pension funds (prefix: 72)
EX_OFFICIO_ASSIGNMENT NIF assigned ex officio to entities not required to register with the RNPC (prefix: 77)
NON_RESIDENT_VAT_REFUND Non-resident entity covered by the EU VAT Refund procedure (prefix: 78)
EXCEPTIONAL_REGIME_EXPO98 Exceptional regime — Expo 98 (prefix: 79)
CONDOMINIUMS_AND_IRREGULAR_ENTITIES Condominiums, irregular partnerships, or undivided estates where the deceased was a sole trader (prefixes: 90, 91)
NON_RESIDENT_NO_ESTABLISHMENT Non-resident entity without a permanent establishment in Portugal (prefix: 98)
CIVIL_PARTNERSHIP_NO_LEGAL_PERSONALITY Civil partnership without legal personality (prefix: 99)

Classification API

Two classification methods must be provided, sharing the same core logic:

classify — for callers that expect classification to sometimes yield no result. Returns Optional.of(type) if classification succeeds, otherwise Optional.empty(). Never fails.

classifyOrThrow — for callers that require a result and treat an unclassifiable NIF as an error. Returns the matched type if classification succeeds, otherwise throws an IllegalArgumentException.

Both invalid NIFs and unrecognized prefixes are treated uniformly as unclassifiable — neither case is distinguished from the other.

Both methods must:

  • Reuse the existing isNif validation logic for structural validation.
  • Operate on a normalized NIF string (trimmed, no whitespace), consistent with how isNif handles input.
  • Apply prefix matching using the longest matching prefix first (e.g., 45 before 4, 70 before 7). The mapping may be implemented as a prefix lookup table ordered by descending prefix length.
  • Base prefix matching on the string representation of the NIF, not numeric parsing, to avoid losing leading digits.
    Legal source: Decreto-Lei n.º 14/2013, of 28 January.
    Available at: https://diariodarepublica.pt/dr/detalhe/decreto-lei/14-2013-257001

Acceptance Criteria

  • Given a valid NIF with a known prefix, both classify and classifyOrThrow return the correct NifType.
  • Given a valid NIF with an unrecognized prefix, classify returns Optional.empty() and classifyOrThrow throws an IllegalArgumentException.
  • Given a structurally invalid NIF, classify returns Optional.empty() and classifyOrThrow throws an IllegalArgumentException.
  • Given a NIF with leading or trailing whitespace, both methods normalize the input before classifying.
  • All 13 entity types defined in Decreto-Lei n.º 14/2013 are supported.
  • Given a NIF whose prefix overlaps with a shorter one, the longest match wins (e.g., 45xxxxxxx resolves to NON_RESIDENT_INDIVIDUAL_WITHHOLDING, not INDIVIDUAL).

Alternatives considered

  • Providing only one classification method — rejected in favor of offering both a safe and a strict variant, to suit different calling contexts.
  • Treating invalid NIFs and unrecognized prefixes differently — rejected to keep the API surface simple and uniform.

Additional context

Official sources consulted to define the NIF type ranges:

Code of Conduct

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions