NFC NDEF Programming Guide for Android — Read & Write NFC Tags
Complete guide to reading and writing NDEF messages on Android. Covers Ndef.get(), NdefFormatable, foreground dispatch, intent filters, and all NDEF record types. Java and Kotlin examples for NTAG, MIFARE Classic, and NFC Forum Type 1-4 tags. Use our NDEF Message Parser to verify your hex output.
1. Add NFC Permissions to AndroidManifest.xml
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2. Reading an NDEF Message from a Tag
When a user taps an NFC tag, Android dispatches an intent. Use Ndef.get() to read the NDEF message.
// In your Activity's onNewIntent()
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())
&& !NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
return;
}
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
// Tag is not NDEF-formatted — use NdefFormatable (see below)
Log.w("NFC", "Tag is not NDEF formatted");
return;
}
try {
ndef.connect();
NdefMessage msg = ndef.getNdefMessage(); // Read the message
NdefRecord[] records = msg.getRecords();
for (NdefRecord record : records) {
byte[] payload = record.getPayload();
short tnf = record.getTnf();
if (tnf == NdefRecord.TNF_WELL_KNOWN) {
// Try parsing as Text
String text = parseTextRecord(record);
Log.d("NFC", "Text record: " + text);
} else if (tnf == NdefRecord.TNF_WELL_KNOWN) {
// Try parsing as URI
String uri = parseUriRecord(record);
if (uri != null) Log.d("NFC", "URI: " + uri);
}
}
ndef.close();
} catch (Exception e) {
Log.e("NFC", "Read failed: " + e.getMessage());
}
}
// Helper: Parse Text Record
String parseTextRecord(NdefRecord record) {
byte[] payload = record.getPayload();
int langLen = payload[0] & 0x3F;
return new String(payload, 1 + langLen, payload.length - 1 - langLen, StandardCharsets.UTF_8);
}
// Helper: Parse URI Record
String parseUriRecord(NdefRecord record) {
byte[] payload = record.getPayload();
String prefix = URI_PREFIX_MAP[payload[0]];
return prefix + new String(payload, 1, payload.length - 1, StandardCharsets.UTF_8);
}
3. Writing NDEF Messages to Tags
// Write a simple Text record to an NFC tag
private void writeTextRecord(Tag tag, String text, String lang) throws Exception {
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
// Tag is not NDEF-formatted — need to format first
NdefFormatable formatable = NdefFormatable.get(tag);
if (formatable != null) {
formatable.connect();
formatable.format(buildTextMessage(text, lang));
formatable.close();
return;
}
throw new Exception("Cannot write to this tag type");
}
ndef.connect();
if (!ndef.isWritable()) {
ndef.close();
throw new Exception("Tag is read-only");
}
ndef.writeNdefMessage(buildTextMessage(text, lang));
ndef.close();
Log.d("NFC", "Successfully wrote NDEF message");
}
// Build a Text NDEF Message
NdefMessage buildTextMessage(String text, String lang) {
byte[] langBytes = lang.getBytes(StandardCharsets.US_ASCII);
byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
byte[] payload = new byte[1 + langBytes.length + textBytes.length];
payload[0] = (byte) langBytes.length; // Status byte
System.arraycopy(langBytes, 0, payload, 1, langBytes.length);
System.arraycopy(textBytes, 0, payload, 1 + langBytes.length, textBytes.length);
NdefRecord record = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT,
new byte[0],
payload
);
return new NdefMessage(new NdefRecord[]{record});
}
4. Writing a URI Record
// Create a URI NDEF message (for Smart Posters, URL sharing)
NdefMessage buildUriMessage(String uri) {
NdefRecord record = NdefRecord.createUri(uri);
return new NdefMessage(new NdefRecord[]{record});
}
// Write URI to tag
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
ndef.writeNdefMessage(buildUriMessage("https://cupass.com/en/"));
ndef.close();
}
NdefRecord.createUri() automatically compresses URIs — https:// becomes prefix byte 0x04, saving 7 bytes on the tag. Use our NDEF Message Parser to see the raw hex structure of your URI records.5. Foreground Dispatch — Read Tags While App is Open
Without foreground dispatch, NFC intents launch your app. With foreground dispatch, your running activity gets the tag data immediately.
public class MainActivity extends AppCompatActivity {
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter[] intentFilters;
private String[][] techLists;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
Toast.makeText(this, "NFC not available", Toast.LENGTH_LONG).show();
finish();
return;
}
pendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE);
intentFilters = new IntentFilter[]{
new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
};
// All NDEF-compatible tech types
techLists = new String[][]{
new String[]{Ndef.class.getName()},
new String[]{NdefFormatable.class.getName()}
};
}
@Override
protected void onResume() {
super.onResume();
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, techLists);
}
@Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
// Read/write the tag as shown above
}
}
6. Making Tags Read-Only
Ndef ndef = Ndef.get(tag);
ndef.connect();
if (!ndef.canMakeReadOnly()) {
Log.w("NFC", "This tag does not support read-only");
ndef.close();
return;
}
// Write your message first, THEN lock
ndef.writeNdefMessage(message);
if (ndef.canMakeReadOnly()) {
ndef.makeReadOnly();
Log.d("NFC", "Tag is now permanently read-only");
}
ndef.close();
7. Common Issues and Debugging
| Problem | Cause | Solution |
|---|---|---|
Ndef.get(tag) returns null | Tag not NDEF formatted (e.g., blank NTAG, locked MIFARE) | Use NdefFormatable.get(tag) to format first |
writeNdefMessage() throws IOException | Message too large for tag | Use our NDEF Writer Simulator to check capacity first |
| App doesn't respond to NFC tap | Missing foreground dispatch or intent filter | Check manifest intent-filters, verify onResume() calls enableForegroundDispatch() |
| Tag "lost" after write | Tag disconnected during operation | Keep the tag on the reader until ndef.close() completes |
| Compatibility issues with MIFARE Classic | MIFARE Classic not fully NDEF compliant | Use MifareClassic tech for raw sector access |
Related Tools and Guides
NDEF Message Parser — Paste NDEF hex, see decoded records | NDEF Writer Simulator — Test your message fits before writing to a tag | NFC Tag Capacity Calculator — Compare tag memory | NFC Tag Type Detector — Identify tag from SAK/ATQA | Best NFC Tags — Which tags to buy for development