The data model and parser contract for scientific names — ParsedName,
Authorship, Rank, NomCode, NameType, the NameParser interface and the
ParseResult it returns — plus name formatting and rank/unicode utilities.
As of 5.0.0 this repository is API-only: it ships the single
name-parser-api artifact. The parsing engine has been reimplemented in Rust
(with the full regression corpus), and is consumed through this interface via a
native binding. The previous Java implementation (NameParserImpl and the
name-parser / name-parser-cli modules) lives on the 4.x branch for
maintenance; see Migrating from 4.x to 5.0.
| Type | Purpose |
|---|---|
ParsedName, ParsedAuthorship, Authorship |
the structured name / authorship model |
Rank, NomCode, NameType, NamePart |
the controlled vocabularies |
NameParser |
the parser contract — returns a ParseResult |
ParseResult (Parsed | Informal | Unparsable) |
the three-way parse outcome; see below |
UnparsableNameException |
unchecked; raised only by ParseResult.orElseThrow() |
NameFormatter |
render a ParsedName, an Informal, or a whole ParseResult (also any CombinedAuthorshipIF) back to a string |
RankUtils, UnicodeUtils |
rank relationships and unicode/homoglyph helpers |
<dependency>
<groupId>org.gbif</groupId>
<artifactId>name-parser-api</artifactId>
<version>5.0.0</version>
</dependency>parse(...) never throws — it returns a sealed, three-way ParseResult:
Parsed— a fully structuredParsedName(itsstate()may beCOMPLETEorPARTIAL).Informal— a semistructured name: a real supraspecifictaxoncarrying a provisional, non-code designation instead of a determined species epithet — a molecular provisional species (Rhizobium sp. RMCC TR1811), a numbered placeholder (Allium sp. 1), or an informal group (Bartonella group). It is a flattaxon/taxonRank/rank/phrase/codeand carries noParsedName— the anchor is unvalidated, so it is never mislabelled as a determined genus. Names that keep a species epithet — including cf./aff. and infraspecific-indeterminate ones — stayParsed, so theirspecificAuthorship(which a flat anchor could not hold) survives.Unparsable— not a scientific name at all: a virus, a hybrid formula, a placeholder, or a machine identifier such as a BOLD BIN / UNITE SH / OTU / culture-collection accession.type()classifies it —FORMULA,PLACEHOLDER,IDENTIFIER, orOTHER.
type() and code() are available on all three variants, so you can classify without catching
anything:
NameParser parser = new NameParserRust(); // the native (Rust-backed) implementation — see below
switch (parser.parse("Rhizobium sp. RMCC TR1811", null, null, null)) {
case ParseResult.Parsed p -> index(p.name()); // a full ParsedName
case ParseResult.Informal i -> indexInformal(i.taxon(), i.phrase()); // "Rhizobium" + "RMCC TR1811"
case ParseResult.Unparsable u -> record(u.type(), u.code()); // type FORMULA/PLACEHOLDER/IDENTIFIER/OTHER, code e.g. VIRUS
}Any variant round-trips back to a string via NameFormatter.canonical(result) — the reconstructed
name for Parsed, the informal name (Rhizobium sp. RMCC TR1811) for Informal, the verbatim
input for Unparsable — or call NameFormatter.canonical(informal) on an Informal directly.
It composes in streams:
List<ParsedName> parsed = names.stream()
.map(n -> parser.parse(n, null, rank, code))
.flatMap(r -> r.parsed().stream()) // keeps only Parsed; Informal + Unparsable have an empty parsed()
.toList();…and offers an opt-in fail-fast path for callers that want it:
ParsedName pn = parser.parse(name).orElseThrow(); // throws unchecked UnparsableNameExceptionOnly need the model or the formatter (no parsing)? Depend on this artifact and
use ParsedName / NameFormatter directly — no implementation required.
From 5.0 the reference implementation lives in a separate project,
gbif/name-parser-rust. The
parser core is a Rust crate (nameparser), which also carries the full
regression corpus ported from the old Java suite, and is exposed through several
bindings — a C-ABI cdylib (nameparser-ffi) and a Python module
(nameparser-py).
Java callers get a NameParser from that project's Panama binding:
org.gbif.nameparser.rust.NameParserRust implements
org.gbif.nameparser.api.NameParser from this module by downcalling the Rust
cdylib in-process via java.lang.foreign (FFM/Panama, stable since JDK 22 — no
--enable-preview). Each parse marshals across the FFI boundary and rebuilds
a ParsedName from a flat binary struct. Because java.lang.foreign needs a
modern JDK, that binding targets JDK 25+ and is
built and released independently of this Java-17 API module — which is exactly
why the API stays on 17: model- and formatter-only consumers keep the broad
baseline, while only the native binding requires the newer JDK.
This api artifact carries no parser on its own. For a working NameParser, add the native
binding — org.gbif.nameparser:name-parser-rust, from
gbif/name-parser-rust — which pulls this
name-parser-api in transitively. It ships as a thin main JAR plus one native classifier JAR per
platform (netty-tcnative style), so you download only your own architecture's cdylib. Requires
JDK 25+.
<build><extensions>
<!-- resolves ${os.detected.classifier}: linux-x86_64, osx-aarch_64, windows-x86_64, … -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions></build>
<dependencies>
<dependency> <!-- thin main JAR: Java + FFM loader (brings name-parser-api) -->
<groupId>org.gbif.nameparser</groupId>
<artifactId>name-parser-rust</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency> <!-- your platform's native cdylib -->
<groupId>org.gbif.nameparser</groupId>
<artifactId>name-parser-rust</artifactId>
<version>0.1.0-SNAPSHOT</version>
<classifier>${os.detected.classifier}</classifier>
</dependency>
</dependencies>The binding is published to GBIF's Nexus; if your build doesn't already resolve from it, add:
<repositories>
<repository>
<id>gbif-all</id>
<url>https://repository.gbif.org/content/groups/gbif</url>
</repository>
</repositories>0.1.0-SNAPSHOT is auto-deployed on every push to main (a released 0.1.0 will follow); the
binding tracks the Rust engine's own version line, independent of this api's 5.0.0. Then just
construct it — no other wiring, and the same NameParser interface as before:
NameParser parser = new org.gbif.nameparser.rust.NameParserRust();5.0 keeps the same model but changes the parser contract and drops the bundled Java engine.
parse(...)returns a three-wayParseResult, notParsedName, and no longer throws. Replacetry { ParsedName pn = parser.parse(…); } catch (UnparsableNameException e) { … }with aswitchoverParsed | Informal | Unparsable, orparser.parse(…).parsed()/.orElseThrow().type()andcode()are on all three variants, so failure classification no longer needs the exception.- Informal / semistructured names are now their own
Informalresult, not aParsedName. Names that 4.x returned as aParsedNamewithtype = INFORMALand no species epithet — a supraspecific taxon plus a provisional designation (Genus sp. <tag>,Bartonella group) — now come back as a flatParseResult.Informal(taxon/taxonRank/rank/phrase/code) that carries noParsedName. Aswitchthat previously handled onlyParsedandUnparsablemust add theInformalarm. (Names that keep a species epithet — cf./aff., strain, infraspecific indet — stayParsedas before.) parseAuthorship(...)returnsOptional<ParsedAuthorship>instead of throwing.UnparsableNameExceptionis now unchecked (extends RuntimeException) and is only raised byParseResult.orElseThrow().- The Java parser is gone from this repo.
new NameParserImpl()no longer exists in 5.0 — obtain aNameParserfrom the native binding instead. If you need the pure-Java engine, stay on the4.xbranch (name-parser4.2.x), which keeps the old throwing API andNameParserImpl.
Model/vocabulary changes introduced across the 4.x line (still current in 5.0)
are documented in the 4.x branch README — notably NameType.VIRUS/OTU
removal (viruses now carry code = VIRUS), Rank.DIVISION → DIVISION_ZOOLOGY
plus a new DIVISION_BOTANY, imprint years moving to Authorship, and the
CombinedAuthorshipIF / publishedInYear / generic-&-specific-authorship
additions.
mvn install from the repo root — a single-module Java 17 build.
Apache 2.0.