GBIF Name Parser
The core GBIF scientific name parser library.
https://github.com/gbif/name-parser
Category: Biosphere
Sub Category: Biodiversity Data Cleaning and Standardization
Keywords from Contributors
biodiversity-informatics darwin-core taxonomy gbif tdwg biodiversity species snapshot interest-group
Last synced: about 8 hours ago
JSON representation
Repository metadata
The core GBIF scientific name parser library
- Host: GitHub
- URL: https://github.com/gbif/name-parser
- Owner: gbif
- License: apache-2.0
- Created: 2014-01-24T10:44:23.000Z (over 12 years ago)
- Default Branch: master
- Last Pushed: 2026-07-14T15:10:40.000Z (27 days ago)
- Last Synced: 2026-08-02T16:04:06.774Z (8 days ago)
- Language: Java
- Size: 3.27 MB
- Stars: 20
- Watchers: 18
- Forks: 4
- Open Issues: 52
- Releases: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
README.md
GBIF Name Parser API
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.
What's in here
| 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 |
Library use
<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 UnparsableNameException
Only need the model or the formatter (no parsing)? Depend on this artifact and
use ParsedName / NameFormatter directly — no implementation required.
The parsing engine (Rust)
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.
Depending on the Rust binding (to actually parse)
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();
Migrating from 4.x to 5.0
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 onlyParsedandUnparsable
must 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.
Build
mvn install from the repo root — a single-module Java 17 build.
License
Apache 2.0.
Owner metadata
- Name: Global Biodiversity Information Facility
- Login: gbif
- Email:
- Kind: organization
- Description:
- Website: https://www.gbif.org
- Location: Copenhagen, Denmark
- Twitter:
- Company:
- Icon url: https://avatars.githubusercontent.com/u/1963797?v=4
- Repositories: 288
- Last ynced at: 2024-04-14T06:45:04.085Z
- Profile URL: https://github.com/gbif
GitHub Events
Total
- Delete event: 3
- Pull request event: 1
- Fork event: 1
- Issues event: 2
- Watch event: 1
- Issue comment event: 4
- Push event: 71
- Create event: 6
Last Year
- Delete event: 1
- Pull request event: 1
- Issues event: 1
- Watch event: 1
- Issue comment event: 2
- Push event: 52
- Create event: 3
Committers metadata
Last synced: 5 days ago
Total Commits: 729
Total Committers: 14
Avg Commits per committer: 52.071
Development Distribution Score (DDS): 0.246
Commits in past year: 157
Committers in past year: 2
Avg Commits per committer in past year: 78.5
Development Distribution Score (DDS) in past year: 0.115
| Name | Commits | |
|---|---|---|
| Markus Döring | m****g@g****g | 550 |
| gbif-jenkins | d****v@g****g | 102 |
| gbif-jenkins | j****s@r****g | 30 |
| gbif-jenkins | j****s@j****g | 18 |
| pal155 | D****r@c****u | 8 |
| Federico Mendez | f****z@g****g | 7 |
| Oliver Meyn | o****r@m****m | 3 |
| Kyle Braak | k****k@g****g | 3 |
| dependabot[bot] | 4****] | 2 |
| Matthew Blissett | m****t@g****g | 2 |
| Thomas Stjernegaard Jeppesen | t****n@g****g | 1 |
| Nikolay Volik | n****k@g****g | 1 |
| Christian Gendreau | c****u | 1 |
| Jorrit Poelen | j****n@g****m | 1 |
Committer domains:
- gbif.org: 7
- mineallmeyn.com: 1
- csiro.au: 1
- jenkins-vh.gbif.org: 1
- rancor.gbif.org: 1
Issue and Pull Request metadata
Last synced: about 1 month ago
Total issues: 4
Total pull requests: 3
Average time to close issues: 1 day
Average time to close pull requests: almost 2 years
Total issue authors: 3
Total pull request authors: 2
Average comments per issue: 1.25
Average comments per pull request: 0.33
Merged pull request: 0
Bot issues: 0
Bot pull requests: 3
Past year issues: 3
Past year pull requests: 1
Past year average time to close issues: 2 days
Past year average time to close pull requests: N/A
Past year issue authors: 3
Past year pull request authors: 1
Past year average comments per issue: 1.33
Past year average comments per pull request: 0.0
Past year merged pull request: 0
Past year bot issues: 0
Past year bot pull requests: 1
Top Issue Authors
- djtfmartin (2)
- CecSve (1)
- mdoering (1)
Top Pull Request Authors
- dependabot[bot] (2)
- renovate[bot] (1)
Top Issue Labels
- bug (1)
Top Pull Request Labels
- dependencies (2)
- java (1)
Dependencies
- com.google.guava:guava
- commons-io:commons-io
- org.apache.commons:commons-lang3
- org.gbif:name-parser-api
- org.slf4j:slf4j-api
- ch.qos.logback:logback-classic test
- junit:junit test
- org.gbif:name-parser-api test
- com.google.code.findbugs:jsr305
- com.google.guava:guava
- org.apache.commons:commons-lang3
- org.slf4j:slf4j-api
- ch.qos.logback:logback-classic test
- commons-io:commons-io test
- junit:junit test
- com.google.code.findbugs:jsr305 3.0.2
- com.google.guava:guava 28.0-jre
- commons-io:commons-io 2.8.0
- org.apache.commons:commons-lang3 3.12.0
- org.gbif:gbif-api 0.166
- org.gbif:name-parser 3.7.3-SNAPSHOT
- org.gbif:name-parser-api 3.7.3-SNAPSHOT
- org.gbif:name-parser-gbif 3.7.3-SNAPSHOT
- org.slf4j:slf4j-api 1.7.24
- ch.qos.logback:logback-classic 1.2.3 test
- junit:junit 4.12 test
- org.gbif:name-parser-api 3.7.3-SNAPSHOT test
- ch.qos.logback:logback-classic *
- com.google.code.gson:gson *
- org.catalogueoflife:coldp *
- org.gbif:name-parser *
- org.slf4j:slf4j-api *
- junit:junit * test
Score: 6.915723448631313