Skip to content
Martin Paljak edited this page Mar 31, 2026 · 13 revisions

apdu4j

Composable Java 17+ library for working with smart cards via APDU command-response pairs.

See the README for quick start, architecture overview, code examples, and Maven coordinates.

Wiki pages

Concepts

BIBO - Bytes In, Bytes Out

The core abstraction. An interface that sends a bag of bytes and receives a bag of bytes (or a BIBOException) as a reply. Command-response. Synchronous.

byte[] transceive(byte[] bytes) throws BIBOException;

Implementations can be PC/SC, Android NFC, OpenMobileAPI, a network socket, a mock for testing - anything that moves bytes. The interface lives in apdu4j-core which has zero javax.smartcardio dependency.

APDUBIBO - typed APDU over BIBO

APDU is an ancient convention for formatting bags of bytes (CLA, INS, P1, P2, LC, LE, SW...). CommandAPDU and ResponseAPDU provide convenience methods for constructing and parsing those byte arrays. APDUBIBO wraps a raw BIBO with typed transmit:

ResponseAPDU transmit(CommandAPDU apdu) throws BIBOException;

which is equivalent to:

new ResponseAPDU(bibo.transceive(new CommandAPDU(...).getBytes()));

Composable decorators

All transport concerns stack via then():

var bibo = transport
        .then(GetResponseWrapper::wrap)              // auto GET RESPONSE on 61xx
        .then(b -> LoggingBIBO.wrap(b, System.out))  // human-readable trace
        .then(b -> DumpingBIBO.wrap(b, dumpStream));  // machine-readable replay
var apdu = new APDUBIBO(bibo);                        // typed APDU access

Protocol handlers (GetResponseWrapper, GetMoreDataWrapper, RetryWithRightLengthWrapper) are also BIBO implementations - they compose the same way. StatefulBIBO<S> handles secure channel state - see Architecture.

Preferences - typed configuration

Preferences is a typed, immutable bag of cross-cutting configuration. Two kinds: Preference.Default has a sensible default value (tunable by any layer), Preference.Parameter has no default (a discovered fact, readonly once set).

var BLOCK_SIZE = Preference.of("gp.block.size", Integer.class, 255, false);
var SCP_VERSION = Preference.parameter("gp.scp.version", GPSecureChannelVersion.class, true);

External configuration resolves through PreferenceProvider - environment variables, system properties, or code. See Architecture for the full treatment.

BIBOSA - transport with metadata

BIBOSA wraps a BIBO with a Preferences sidecar. It implements BIBO, so it drops in anywhere a plain transport is expected, but also carries typed metadata that accumulates as you build the stack.

var stack = new BIBOSA(transport)
        .then(GetResponseWrapper::wrap)      // Function<BIBO, BIBO> - preferences unchanged
        .then(secureChannelMiddleware);       // BIBOMiddleware - wraps transport AND adds preferences

A BIBOMiddleware receives the entire BIBOSA and returns a new one with potentially different transport AND enriched preferences. For runtime-conditional composition, adapt() reads accumulated preferences to decide which middleware to apply. See Architecture.

Apdulette - composable recipes (Java 21+)

A Recipe<T> is a pure description of a card interaction that produces a value of type T. No I/O happens until a Chef executes it. SousChef executes over a real BIBO, MiseEnPlaceChef dry-runs with expected responses.

var recipe = Cookbook.selectFCI(aid)
        .then(fci -> authenticate(keys))
        .then(auth -> load(capFileBytes));

var dish = chef.serve(recipe, prefs);

KitchenManager is the chef factory - PCSCKitchenManager binds recipes to PC/SC readers. See Architecture.

Sessions and channels

A session is a series of APDU transmits. A technical session breaks when BIBOException is thrown. A logical session is the application's responsibility (interpretation of APDU-s).

A channel is logical multiplexing on top of the physical BIBO. SE access is still serialized to a single command-response channel. CardBIBO in apdu4j-pcsc manages javax.smartcardio logical channels transparently by inspecting the CLA byte.

PC/SC, terminals and javax.smartcardio

PC/SC is the ages-old desktop C API for enumerating smart card readers and talking to cards. javax.smartcardio is the Java adaptation. jnasmartcardio is the JNA re-implementation that fixes the many bugs in SunPCSC.

apdu4j uses jnasmartcardio as the preferred provider and adds per-reader single-threaded executors on top, so PC/SC operations for a given reader never race. The fluent reader API (Readers.select().withCard().run(bibo -> ...)) handles all the ceremony.

Modern environments like Android NFC have no notion of PC/SC, but the "reader" concept carries on to API-s like OpenMobileAPI. apdu4j-core works in all of these - PC/SC is optional via apdu4j-pcsc.

Modules

apdu4j-apdulette-pcsc  PCSCKitchenManager (Java 21+)
    |       \
apdu4j-apdulette    apdu4j-pcsc       Recipe/Chef + PC/SC readers
    |               |       \
    |          apdu4j-pcsc-sim  \     Synthesized card terminals
    |               |          /
apdu4j-core                          BIBO, APDUBIBO, BIBOSA, decorators, StatefulBIBO
    |
apdu4j-prefs                         Preference, Preferences, PreferenceProvider

apdu4j-tool                          Command line interface
apdu4j-report                        Aggregate test coverage

Use apdu4j-pcsc for desktop applications (includes apdu4j-core and apdu4j-prefs transitively). Use apdu4j-core alone for Android, embedded, or test-only scenarios. Add apdu4j-apdulette for composable recipe-based card interaction (requires Java 21+). Add apdu4j-apdulette-pcsc to bind recipes to PC/SC readers via KitchenManager.

There is also apdu4j-tool which provides a command line interface - see README.