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();
}
URI prefix abbreviations: Android's 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

ProblemCauseSolution
Ndef.get(tag) returns nullTag not NDEF formatted (e.g., blank NTAG, locked MIFARE)Use NdefFormatable.get(tag) to format first
writeNdefMessage() throws IOExceptionMessage too large for tagUse our NDEF Writer Simulator to check capacity first
App doesn't respond to NFC tapMissing foreground dispatch or intent filterCheck manifest intent-filters, verify onResume() calls enableForegroundDispatch()
Tag "lost" after writeTag disconnected during operationKeep the tag on the reader until ndef.close() completes
Compatibility issues with MIFARE ClassicMIFARE Classic not fully NDEF compliantUse 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