Codename One ships an NFC API under com.codename1.nfc that covers the three things most apps need: read and write NDEF tags, exchange APDUs with ISO 7816 / MIFARE / FeliCa smart cards, and act as a host-emulated card for a nearby reader/terminal.
A single entry point exposes the platform NFC controller, a typed enum reports the technologies the discovered tag supports, and a typed error enum lets callers react to failures without string matching.
Quick start: Read an NDEF URI
Nfc nfc = Nfc.getInstance();
if (!nfc.canRead()) {
// no NFC hardware, or it is disabled in system settings
return;
}
nfc.readNdef(new NfcReadOptions()
.setNdefOnly(true)
.setAlertMessage("Hold near the poster"))
.onResult((NdefMessage msg, Throwable err) -> {
if (err != null) {
return;
}
String url = msg.getFirstRecord().getUriPayload();
Display.getInstance().execute(url);
});
setNdefOnly(true) picks the fastest iOS Core NFC session (NFCNDEFReaderSession) and matches the most common tag layout. For mixed payloads use readTag(…) and inspect tag.getTypes().
Quick start: Write an NDEF URI
NDEF records carry one of the well-known types (T, U, Sp), a MIME media type, an absolute URI, or an external domain:type. Use the factory methods on NdefRecord rather than constructing raw byte arrays:
| Factory | Use for |
|---|---|
| URI records — launches the associated app on tap |
| Human-readable text |
| Binary payloads (vCard, JSON, …) |
| Custom vendor types |
| Android Application Record (AAR) |
Tag-technology APIs
Beyond NDEF, the discovered Tag exposes accessors for the underlying technologies:
nfc.readTag(new NfcReadOptions()
.setTechFilter(TagType.ISO_DEP)
.setIsoSelectAids(myAid))
.onResult((Tag tag, Throwable err) -> {
if (err != null) return;
IsoDep iso = tag.getIsoDep();
if (iso == null) return;
iso.transceive(myCommandApdu).onResult((byte[] resp, Throwable e) -> {
if (ApduResponse.isSuccess(resp)) {
byte[] body = ApduResponse.body(resp);
// application-specific parsing
}
});
});
Each accessor returns null when the tag doesn’t advertise the corresponding technology, so always null-check before calling.
setIsoSelectAids must also be listed in
Info.plist under
com.apple.developer.nfc.readersession.iso7816.select-identifiers. Core NFC only
discovers an application the app declared up front, and the builders can’t read a
runtime argument, so inject that key with ios.plistInject too. The
nfc.hce.iso7816.select-identifiers entitlement the builders do set is the
host-card-emulation key, which is a different thing.| Accessor | Android | iOS | Notes |
|---|---|---|---|
| yes | yes | ISO 7816 — EMV, ePassport, smart cards |
| yes | — | Apple doesn’t expose MIFARE Classic on iOS |
| yes | yes | NTAG21x, Ultralight C |
| yes | yes (limited) | Raw ISO 14443-3A |
| yes | — | Raw ISO 14443-3B (Android-only) |
| yes | yes | FeliCa (Suica / PASMO / ICOCA) |
| yes | — | ISO 15693 (Android-only) |
For FeliCa on iOS, set the system codes in NfcReadOptions:
NfcReadOptions options = new NfcReadOptions()
.setTechFilter(TagType.NFC_F)
.setFelicaSystemCodes("0003", "8008");
nfc.readTag(options);
The builders can’t derive that from the call — the codes are a runtime argument
they never see. iOS wants them in Info.plist, so inject the key at build time:
codename1.arg.ios.plistInject=<key>com.apple.developer.nfc.readersession.felica.systemcodes</key><array><string>0003</string><string>8008</string></array>
Seeing com.codename1.nfc on the classpath gets you CoreNFC, a default
NFCReaderUsageDescription and the
com.apple.developer.nfc.readersession.formats entitlement automatically. These
system codes are the one part you supply.
Quick start: Host card emulation
Host Card Emulation (HCE) lets the device pretend to be a contactless smart card. A nearby reader (Android phone, payment terminal, access control gate) sends ISO 7816 APDUs and your app responds. Subclass HostCardEmulationService and register the instance:
class MyService extends HostCardEmulationService {
@Override
public String[] getAids() {
return new String[] { "F0010203040506" };
}
@Override
public byte[] processCommand(byte[] apdu) {
if (apdu.length > 1 && apdu[1] == (byte) 0xA4) {
// SELECT -- terminal has just routed an APDU to our AID
return ApduResponse.withStatus(
new byte[] { 'O', 'K' },
ApduResponse.swSuccess());
}
return ApduResponse.swInsNotSupported();
}
}
Nfc.getInstance().registerHostCardEmulationService(new MyService());
Set the AIDs as a build hint so they appear in the platform routing tables:
android.hceAids=F0010203040506
android.hceCategory=other
The Maven plugin and BuildDaemon generate apduservice.xml on Android, register the CodenameOneHostApduService in the manifest, and inject the iOS HCE entitlement so the same code path works on both platforms.
iOS HCE (CardSession) requires iOS 17.4+ and, as of 2026, is EU-only. The Codename One plugin still injects the entitlement when you reference the class, but canHostEmulate() will report false on non-EU devices.
Permissions and build hints
The Codename One build pipeline automatically detects NFC usage. Apps that never touch com.codename1.nfc see no manifest / plist change.
| Reference | Android injected | iOS injected |
|---|---|---|
|
|
|
|
|
|
Override any of the defaults with the matching build hint:
| Build hint | Default | Notes |
|---|---|---|
| "Hold near an NFC tag to continue" | Localise this — Apple rejects builds that ship the default copy |
|
| Multi-line entitlement value |
| (none) | Comma-separated; required for HCE on Android |
|
|
|
|
| Force lock screen unlock before APDU routing |
| falls back to | iOS HCE AID list |
Simulator support
The JavaSE simulator implements Nfc via a virtual tag you can edit and tap from the Simulate → NFC submenu:
| Menu item | Effect |
|---|---|
Hardware Available / NFC Enabled / HCE Available | Toggle what |
Next read outcome | Pick |
Tap virtual tag | Fire the configured outcome on any pending read or registered listener |
Set virtual tag URI… | Replace the virtual tag’s NDEF payload with a URI record |
Set virtual tag text… | Replace the virtual tag’s NDEF payload with a text record |
Tag is read-only | Lock the virtual tag so the next write fails with |
Send APDU to HCE service… | Hex-entry dialog that dispatches the bytes to your registered |
Deactivate HCE field | Fires |
All toggles persist between simulator runs (under NfcSim.* user preferences) so you can drive tests deterministically.
Platform behaviour
| Platform | NDEF read | NDEF write | Tag tech | HCE | Notes |
|---|---|---|---|---|---|
Android | yes | yes | full set | yes |
|
iOS | yes (iOS 11+) | yes (iOS 13+) | ISO 7816, FeliCa, MIFARE Ultralight | iOS 17.4+ EU-only | MIFARE Classic and NFC-B/V not exposed by Apple |
JavaSE simulator | yes (virtual tag) | yes (virtual tag) | configurable | yes (synthetic APDUs) | Drive everything from the Simulate → NFC menu |
Desktop deploy / JavaScript | no | no | no | no | Fallback base class — every method completes with |
Always gate user-facing affordances on canRead() (for tag operations) or canHostEmulate() (for HCE). isSupported() only reports whether the hardware is present.