google-site-verification: google61fe8ba583a51912.html
NFC Reader with Java: Complete Implementation Guide

NFC (Near Field Communication) enables short-range wireless communication between devices. Here's a comprehensive guide to implementing NFC reading and writing capabilities in Java.


NFC Architecture Overview

Key NFC Technologies

  • NFC-A/B/F: Different communication protocols
  • NDEF (NFC Data Exchange Format): Standard data format
  • MIME Types: Data type identification
  • NFC Tags: Types 1-5 with different capabilities

Java NFC APIs

  • javax.smartcardio: Basic smart card communication
  • JSR 257 (Obsolete): Former NFC API
  • Third-party Libraries: ACS, Spring NFC, etc.
  • Android NFC: Android-specific API

Basic Smart Card IO Implementation

Example 1: Basic NFC Reader using javax.smartcardio

import javax.smartcardio.*; import java.util.List; public class BasicNFCReader { private TerminalFactory terminalFactory; private List<CardTerminal> terminals; private CardTerminal terminal; private Card card; private CardChannel channel; public BasicNFCReader() { initializeReader(); } private void initializeReader() { try { // Get terminal factory terminalFactory = TerminalFactory.getDefault(); terminals = terminalFactory.terminals().list(); if (terminals.isEmpty()) { System.out.println("No NFC readers found"); return; } // Use first available terminal terminal = terminals.get(0); System.out.println("Using reader: " + terminal.getName()); } catch (CardException e) { System.err.println("Error initializing NFC reader: " + e.getMessage()); } } public boolean waitForCard() { if (terminal == null) { System.err.println("No terminal available"); return false; } try { System.out.println("Waiting for NFC card..."); terminal.waitForCardPresent(0); // Wait indefinitely return true; } catch (CardException e) { System.err.println("Error waiting for card: " + e.getMessage()); return false; } } public boolean connectToCard() { try { if (terminal.isCardPresent()) { card = terminal.connect("T=1"); // T=0 for contact, T=1 for contactless channel = card.getBasicChannel(); System.out.println("Connected to card: " + card); return true; } } catch (CardException e) { System.err.println("Error connecting to card: " + e.getMessage()); } return false; } public byte[] sendAPDU(byte[] command) { if (channel == null) { System.err.println("Not connected to card"); return null; } try { CommandAPDU apdu = new CommandAPDU(command); ResponseAPDU response = channel.transmit(apdu); return response.getBytes(); } catch (CardException e) { System.err.println("Error sending APDU: " + e.getMessage()); return null; } } public void disconnect() { try { if (card != null) { card.disconnect(false); card = null; channel = null; System.out.println("Disconnected from card"); } } catch (CardException e) { System.err.println("Error disconnecting: " + e.getMessage()); } } public void waitForCardRemoval() { try { if (terminal != null) { terminal.waitForCardAbsent(0); System.out.println("Card removed"); } } catch (CardException e) { System.err.println("Error waiting for card removal: " + e.getMessage()); } } // Common NFC commands public byte[] selectApplication(byte[] aid) { // SELECT command APDU byte[] selectCommand = new byte[5 + aid.length]; selectCommand[0] = (byte) 0x00; // CLA selectCommand[1] = (byte) 0xA4; // INS: SELECT selectCommand[2] = (byte) 0x04; // P1: Select by name selectCommand[3] = (byte) 0x00; // P2: First or only occurrence selectCommand[4] = (byte) aid.length; // Lc: Length of AID System.arraycopy(aid, 0, selectCommand, 5, aid.length); return sendAPDU(selectCommand); } public byte[] readBinary(int offset, int length) { byte[] readCommand = new byte[5]; readCommand[0] = (byte) 0x00; // CLA readCommand[1] = (byte) 0xB0; // INS: READ BINARY readCommand[2] = (byte) ((offset >> 8) & 0xFF); // P1: High byte of offset readCommand[3] = (byte) (offset & 0xFF); // P2: Low byte of offset readCommand[4] = (byte) length; // Le: Expected length return sendAPDU(readCommand); } public static void main(String[] args) { BasicNFCReader reader = new BasicNFCReader(); try { while (true) { if (reader.waitForCard()) { if (reader.connectToCard()) { // Try to read card UID (varies by card type) byte[] response = reader.selectApplication(new byte[]{ (byte) 0xF0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }); if (response != null) { System.out.println("Card response: " + bytesToHex(response)); } reader.disconnect(); reader.waitForCardRemoval(); } } } } catch (Exception e) { e.printStackTrace(); } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString(); } }

Advanced NFC Framework

Example 2: Comprehensive NFC Framework

import javax.smartcardio.*; import java.util.*; import java.util.concurrent.*; public class AdvancedNFCManager { private final List<CardTerminal> terminals; private final Map<String, NFCCardHandler> cardHandlers; private final ScheduledExecutorService scheduler; private final List<NFCEventListener> listeners; private boolean monitoring; public AdvancedNFCManager() { this.terminals = new ArrayList<>(); this.cardHandlers = new HashMap<>(); this.scheduler = Executors.newScheduledThreadPool(2); this.listeners = new ArrayList<>(); this.monitoring = false; initializeHandlers(); discoverTerminals(); } private void initializeHandlers() { // Register handlers for different card types cardHandlers.put("MIFARE_CLASSIC", new MifareClassicHandler()); cardHandlers.put("MIFARE_ULTRALIGHT", new MifareUltralightHandler()); cardHandlers.put("ISO14443A", new ISO14443AHandler()); cardHandlers.put("NDEF", new NDEFHandler()); cardHandlers.put("UNKNOWN", new UnknownCardHandler()); } private void discoverTerminals() { try { TerminalFactory factory = TerminalFactory.getDefault(); List<CardTerminal> discovered = factory.terminals().list(); terminals.clear(); terminals.addAll(discovered); System.out.println("Discovered " + terminals.size() + " NFC reader(s):"); for (CardTerminal terminal : terminals) { System.out.println(" - " + terminal.getName()); } } catch (CardException e) { System.err.println("Error discovering terminals: " + e.getMessage()); } } public void startMonitoring() { if (monitoring) { System.out.println("Already monitoring"); return; } monitoring = true; System.out.println("Starting NFC monitoring..."); // Schedule terminal discovery scheduler.scheduleAtFixedRate(this::discoverTerminals, 0, 30, TimeUnit.SECONDS); // Start monitoring each terminal for (CardTerminal terminal : terminals) { startTerminalMonitoring(terminal); } } public void stopMonitoring() { monitoring = false; scheduler.shutdown(); System.out.println("NFC monitoring stopped"); } private void startTerminalMonitoring(CardTerminal terminal) { scheduler.execute(() -> { while (monitoring) { try { if (terminal.waitForCardPresent(1000)) { handleCardPresent(terminal); } if (terminal.waitForCardAbsent(1000)) { handleCardRemoved(terminal); } } catch (CardException e) { if (monitoring) { System.err.println("Error monitoring terminal " + terminal.getName() + ": " + e.getMessage()); } } } }); } private void handleCardPresent(CardTerminal terminal) { try { Card card = terminal.connect("T=1"); NFCCardInfo cardInfo = identifyCard(card); System.out.println("Card detected: " + cardInfo); // Notify listeners for (NFCEventListener listener : listeners) { listener.onCardDetected(cardInfo); } // Handle card based on type NFCCardHandler handler = cardHandlers.get(cardInfo.getType()); if (handler != null) { handler.handleCard(card, cardInfo); } else { cardHandlers.get("UNKNOWN").handleCard(card, cardInfo); } card.disconnect(false); } catch (CardException e) { System.err.println("Error handling card on " + terminal.getName() + ": " + e.getMessage()); } } private void handleCardRemoved(CardTerminal terminal) { System.out.println("Card removed from " + terminal.getName()); for (NFCEventListener listener : listeners) { listener.onCardRemoved(terminal.getName()); } } private NFCCardInfo identifyCard(Card card) { NFCCardInfo info = new NFCCardInfo(); info.setTerminalName(card.getTerminal().getName()); info.setProtocol(card.getProtocol()); try { // Try different identification methods CardChannel channel = card.getBasicChannel(); // Send SELECT command to identify card type byte[] selectResponse = sendAPDU(channel, new byte[]{ (byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x00 }); if (selectResponse != null) { analyzeATR(card.getATR().getBytes(), info); analyzeSelectResponse(selectResponse, info); } } catch (Exception e) { System.err.println("Error identifying card: " + e.getMessage()); } return info; } private void analyzeATR(byte[] atr, NFCCardInfo info) { // Analyze Answer To Reset to identify card type if (atr.length > 0) { info.setAtrHex(bytesToHex(atr)); // Simple ATR analysis (simplified) if (atr.length >= 2) { if (atr[0] == (byte) 0x3B && (atr[1] == (byte) 0x8F || atr[1] == (byte) 0x80)) { info.setType("MIFARE_CLASSIC"); } else if (atr[0] == (byte) 0x3B && atr[1] == (byte) 0x88) { info.setType("MIFARE_ULTRALIGHT"); } else { info.setType("ISO14443A"); } } } } private void analyzeSelectResponse(byte[] response, NFCCardInfo info) { if (response.length >= 2) { int sw1 = response[response.length - 2] & 0xFF; int sw2 = response[response.length - 1] & 0xFF; info.setStatusWord((sw1 << 8) | sw2); if (sw1 == 0x90 && sw2 == 0x00) { info.setStatus("SUCCESS"); } else { info.setStatus("ERROR: " + String.format("%02X%02X", sw1, sw2)); } } } private byte[] sendAPDU(CardChannel channel, byte[] command) { try { CommandAPDU apdu = new CommandAPDU(command); ResponseAPDU response = channel.transmit(apdu); return response.getBytes(); } catch (CardException e) { System.err.println("APDU error: " + e.getMessage()); return null; } } public void addEventListener(NFCEventListener listener) { listeners.add(listener); } public void removeEventListener(NFCEventListener listener) { listeners.remove(listener); } public List<String> getTerminalNames() { List<String> names = new ArrayList<>(); for (CardTerminal terminal : terminals) { names.add(terminal.getName()); } return names; } // Utility method private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b)); } return sb.toString(); } public static void main(String[] args) { AdvancedNFCManager manager = new AdvancedNFCManager(); // Add event listener manager.addEventListener(new NFCEventListener() { @Override public void onCardDetected(NFCCardInfo cardInfo) { System.out.println("EVENT: Card detected - " + cardInfo); } @Override public void onCardRemoved(String terminalName) { System.out.println("EVENT: Card removed from " + terminalName); } @Override public void onError(String errorMessage) { System.err.println("EVENT: Error - " + errorMessage); } }); // Start monitoring manager.startMonitoring(); // Keep running for 5 minutes try { Thread.sleep(300000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } manager.stopMonitoring(); } } // Supporting classes and interfaces interface NFCEventListener { void onCardDetected(NFCCardInfo cardInfo); void onCardRemoved(String terminalName); void onError(String errorMessage); } interface NFCCardHandler { void handleCard(Card card, NFCCardInfo cardInfo); } class NFCCardInfo { private String terminalName; private String protocol; private String type; private String atrHex; private int statusWord; private String status; private Map<String, Object> additionalInfo; public NFCCardInfo() { this.additionalInfo = new HashMap<>(); } // Getters and setters public String getTerminalName() { return terminalName; } public void setTerminalName(String terminalName) { this.terminalName = terminalName; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getAtrHex() { return atrHex; } public void setAtrHex(String atrHex) { this.atrHex = atrHex; } public int getStatusWord() { return statusWord; } public void setStatusWord(int statusWord) { this.statusWord = statusWord; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Map<String, Object> getAdditionalInfo() { return additionalInfo; } public void setAdditionalInfo(Map<String, Object> additionalInfo) { this.additionalInfo = additionalInfo; } @Override public String toString() { return String.format("NFCCardInfo{terminal='%s', type='%s', protocol='%s', status='%s'}", terminalName, type, protocol, status); } } // Card handler implementations class MifareClassicHandler implements NFCCardHandler { @Override public void handleCard(Card card, NFCCardInfo cardInfo) { System.out.println("Handling MIFARE Classic card"); try { CardChannel channel = card.getBasicChannel(); // MIFARE Classic specific operations // Note: Actual implementation would require proper authentication // Try to read first block byte[] readCommand = new byte[]{ (byte) 0xFF, (byte) 0xB0, (byte) 0x00, (byte) 0x00, (byte) 0x10 }; byte[] response = sendAPDU(channel, readCommand); if (response != null && response.length > 2) { byte[] data = Arrays.copyOf(response, response.length - 2); cardInfo.getAdditionalInfo().put("block_0_data", bytesToHex(data)); System.out.println("Block 0 data: " + bytesToHex(data)); } } catch (Exception e) { System.err.println("Error handling MIFARE Classic: " + e.getMessage()); } } private byte[] sendAPDU(CardChannel channel, byte[] command) { try { CommandAPDU apdu = new CommandAPDU(command); ResponseAPDU response = channel.transmit(apdu); return response.getBytes(); } catch (CardException e) { return null; } } } class MifareUltralightHandler implements NFCCardHandler { @Override public void handleCard(Card card, NFCCardInfo cardInfo) { System.out.println("Handling MIFARE Ultralight card"); try { CardChannel channel = card.getBasicChannel(); // Read first few pages for (int page = 0; page < 4; page++) { byte[] readCommand = new byte[]{ (byte) 0xFF, (byte) 0xB0, (byte) 0x00, (byte) page, (byte) 0x10 }; byte[] response = sendAPDU(channel, readCommand); if (response != null && response.length > 2) { byte[] data = Arrays.copyOf(response, response.length - 2); cardInfo.getAdditionalInfo().put("page_" + page, bytesToHex(data)); System.out.println("Page " + page + ": " + bytesToHex(data)); } } } catch (Exception e) { System.err.println("Error handling MIFARE Ultralight: " + e.getMessage()); } } private byte[] sendAPDU(CardChannel channel, byte[] command) { try { CommandAPDU apdu = new CommandAPDU(command); ResponseAPDU response = channel.transmit(apdu); return response.getBytes(); } catch (CardException e) { return null; } } } class ISO14443AHandler implements NFCCardHandler { @Override public void handleCard(Card card, NFCCardInfo cardInfo) { System.out.println("Handling ISO14443A card"); // Generic ISO14443A handling } } class NDEFHandler implements NFCCardHandler { @Override public void handleCard(Card card, NFCCardInfo cardInfo) { System.out.println("Handling NDEF card"); // NDEF message parsing and handling } } class UnknownCardHandler implements NFCCardHandler { @Override public void handleCard(Card card, NFCCardInfo cardInfo) { System.out.println("Handling unknown card type"); // Generic handling for unknown cards } } // Utility method class NFCUtils { public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } }

NDEF Message Processing

Example 3: NDEF Message Reader/Writer

import java.nio.charset.StandardCharsets; import java.util.*; public class NDEFProcessor { public static class NDEFMessage { private List<NDEFRecord> records; public NDEFMessage() { this.records = new ArrayList<>(); } public void addRecord(NDEFRecord record) { records.add(record); } public List<NDEFRecord> getRecords() { return records; } public byte[] toBytes() { // Simplified NDEF message encoding List<byte[]> recordBytes = new ArrayList<>(); int totalLength = 0; for (int i = 0; i < records.size(); i++) { NDEFRecord record = records.get(i); byte[] recordData = record.toBytes(i == 0, i == records.size() - 1); recordBytes.add(recordData); totalLength += recordData.length; } byte[] message = new byte[totalLength]; int position = 0; for (byte[] recordData : recordBytes) { System.arraycopy(recordData, 0, message, position, recordData.length); position += recordData.length; } return message; } public static NDEFMessage fromBytes(byte[] data) { NDEFMessage message = new NDEFMessage(); int position = 0; while (position < data.length) { NDEFRecord record = NDEFRecord.fromBytes(data, position); if (record != null) { message.addRecord(record); position += record.getRecordLength(); } else { break; } } return message; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("NDEF Message with ").append(records.size()).append(" records:\n"); for (int i = 0; i < records.size(); i++) { sb.append("Record ").append(i).append(": ").append(records.get(i)).append("\n"); } return sb.toString(); } } public static class NDEFRecord { private boolean messageBegin; private boolean messageEnd; private boolean chunked; private boolean shortRecord; private boolean idLengthPresent; private int typeNameFormat; private int payloadLength; private byte[] type; private byte[] id; private byte[] payload; public NDEFRecord(int typeNameFormat, byte[] type, byte[] payload) { this.typeNameFormat = typeNameFormat; this.type = type; this.payload = payload; this.payloadLength = payload.length; this.id = new byte[0]; } public byte[] toBytes(boolean isFirst, boolean isLast) { messageBegin = isFirst; messageEnd = isLast; shortRecord = payloadLength <= 255; idLengthPresent = id.length > 0; int recordLength = 1 + 1 + type.length + payloadLength; // Header + type length + type + payload if (idLengthPresent) recordLength += 1 + id.length; // ID length + ID if (!shortRecord) recordLength += 4; // Extended payload length byte[] record = new byte[recordLength]; int position = 0; // Record header byte header = (byte) ( (messageBegin ? 0x80 : 0x00) | (messageEnd ? 0x40 : 0x00) | (chunked ? 0x20 : 0x00) | (shortRecord ? 0x10 : 0x00) | (idLengthPresent ? 0x08 : 0x00) | (typeNameFormat & 0x07) ); record[position++] = header; // Type length record[position++] = (byte) type.length; // Payload length if (shortRecord) { record[position++] = (byte) payloadLength; } else { record[position++] = (byte) 0xFF; // 4-byte payload length (big-endian) record[position++] = (byte) ((payloadLength >> 24) & 0xFF); record[position++] = (byte) ((payloadLength >> 16) & 0xFF); record[position++] = (byte) ((payloadLength >> 8) & 0xFF); record[position++] = (byte) (payloadLength & 0xFF); } // ID length (if present) if (idLengthPresent) { record[position++] = (byte) id.length; } // Type System.arraycopy(type, 0, record, position, type.length); position += type.length; // ID (if present) if (idLengthPresent) { System.arraycopy(id, 0, record, position, id.length); position += id.length; } // Payload System.arraycopy(payload, 0, record, position, payloadLength); return record; } public static NDEFRecord fromBytes(byte[] data, int start) { if (data.length - start < 3) return null; NDEFRecord record = new NDEFRecord(); int position = start; // Parse header byte header = data[position++]; record.messageBegin = (header & 0x80) != 0; record.messageEnd = (header & 0x40) != 0; record.chunked = (header & 0x20) != 0; record.shortRecord = (header & 0x10) != 0; record.idLengthPresent = (header & 0x08) != 0; record.typeNameFormat = header & 0x07; // Type length int typeLength = data[position++] & 0xFF; // Payload length if (record.shortRecord) { record.payloadLength = data[position++] & 0xFF; } else { position++; // Skip 0xFF record.payloadLength = ((data[position++] & 0xFF) << 24) | ((data[position++] & 0xFF) << 16) | ((data[position++] & 0xFF) << 8) | (data[position++] & 0xFF); } // ID length int idLength = 0; if (record.idLengthPresent) { idLength = data[position++] & 0xFF; } // Type record.type = new byte[typeLength]; System.arraycopy(data, position, record.type, 0, typeLength); position += typeLength; // ID if (idLength > 0) { record.id = new byte[idLength]; System.arraycopy(data, position, record.id, 0, idLength); position += idLength; } else { record.id = new byte[0]; } // Payload record.payload = new byte[record.payloadLength]; System.arraycopy(data, position, record.payload, 0, record.payloadLength); return record; } public int getRecordLength() { int length = 1 + 1 + type.length + payloadLength; // Header + type length + type + payload if (idLengthPresent) length += 1 + id.length; if (!shortRecord) length += 4; return length; } // Factory methods for common record types public static NDEFRecord createTextRecord(String text, String language) { byte[] languageBytes = language.getBytes(StandardCharsets.US_ASCII); byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); byte[] payload = new byte[1 + languageBytes.length + textBytes.length]; payload[0] = (byte) languageBytes.length; // Status byte with language code length System.arraycopy(languageBytes, 0, payload, 1, languageBytes.length); System.arraycopy(textBytes, 0, payload, 1 + languageBytes.length, textBytes.length); return new NDEFRecord(1, "T".getBytes(StandardCharsets.US_ASCII), payload); } public static NDEFRecord createUriRecord(String uri) { byte[] uriBytes = uri.getBytes(StandardCharsets.UTF_8); byte[] payload = new byte[1 + uriBytes.length]; payload[0] = 0x00; // No abbreviation System.arraycopy(uriBytes, 0, payload, 1, uriBytes.length); return new NDEFRecord(1, "U".getBytes(StandardCharsets.US_ASCII), payload); } public String getText() { if (typeNameFormat == 1 && Arrays.equals(type, "T".getBytes(StandardCharsets.US_ASCII))) { if (payload.length > 0) { int languageLength = payload[0] & 0x3F; return new String(payload, 1 + languageLength, payload.length - 1 - languageLength, StandardCharsets.UTF_8); } } return null; } public String getUri() { if (typeNameFormat == 1 && Arrays.equals(type, "U".getBytes(StandardCharsets.US_ASCII))) { if (payload.length > 0) { return new String(payload, 1, payload.length - 1, StandardCharsets.UTF_8); } } return null; } @Override public String toString() { String typeStr = new String(type, StandardCharsets.US_ASCII); switch (typeNameFormat) { case 1: // NFC Forum well-known type if ("T".equals(typeStr)) { return "Text: " + getText(); } else if ("U".equals(typeStr)) { return "URI: " + getUri(); } break; case 2: // Media-type return "MIME: " + typeStr + " (" + payload.length + " bytes)"; } return String.format("Type: %s, TNF: %d, Payload: %d bytes", typeStr, typeNameFormat, payloadLength); } // Default constructor for parsing private NDEFRecord() {} } public static void main(String[] args) { // Create NDEF message with text and URI records NDEFMessage message = new NDEFMessage(); message.addRecord(NDEFRecord.createTextRecord("Hello NFC!", "en")); message.addRecord(NDEFRecord.createUriRecord("https://example.com")); // Convert to bytes byte[] messageBytes = message.toBytes(); System.out.println("NDEF Message bytes: " + NFCUtils.bytesToHex(messageBytes)); // Parse back from bytes NDEFMessage parsedMessage = NDEFMessage.fromBytes(messageBytes); System.out.println(parsedMessage); } }

Maven Dependencies and Configuration

pom.xml for NFC Project

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>nfc-reader</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- SmartCard IO (included in JDK) --> <!-- No additional dependency needed --> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20231013</version> </dependency> <!-- Apache Commons --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.13.0</version> </dependency> <!-- Testing --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.nfc.NFCMain</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>

Platform-Specific Considerations

Windows Setup

public class WindowsNFCConfig { // PC/SC readers on Windows static { // May need to load native libraries System.loadLibrary("winscard"); } public static void configureWindowsReader() { // Windows-specific configuration System.setProperty("sun.security.smartcardio.library", "winscard"); } }

Linux Setup

public class LinuxNFCConfig { static { // Linux PC/SC configuration System.setProperty("sun.security.smartcardio.library", "/usr/lib/x86_64-linux-gnu/libpcsclite.so.1"); } public static void checkPermissions() { // Ensure user has access to PC/SC daemon File pcscdSocket = new File("/var/run/pcscd/pcscd.comm"); if (!pcscdSocket.canRead()) { System.err.println("Warning: May not have access to PC/SC daemon"); System.err.println("Consider adding user to pcscd group"); } } }

Security Considerations

Example 4: Secure NFC Operations

import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.security.*; public class SecureNFCOperations { private final KeyPair cryptoKeyPair; private final Mac hmac; public SecureNFCOperations() throws NoSuchAlgorithmException { // Generate crypto keys for secure operations KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); this.cryptoKeyPair = keyGen.generateKeyPair(); // Initialize HMAC for message authentication this.hmac = Mac.getInstance("HmacSHA256"); } public byte[] generateSecureAPDU(byte[] command, byte[] key) throws InvalidKeyException { // Add MAC to command for authentication hmac.init(new SecretKeySpec(key, "HmacSHA256")); byte[] mac = hmac.doFinal(command); // Combine command and MAC byte[] secureCommand = new byte[command.length + mac.length]; System.arraycopy(command, 0, secureCommand, 0, command.length); System.arraycopy(mac, 0, secureCommand, command.length, mac.length); return secureCommand; } public boolean verifyResponse(byte[] response, byte[] expectedMac, byte[] key) throws InvalidKeyException { if (response.length < expectedMac.length) return false; hmac.init(new SecretKeySpec(key, "HmacSHA256")); byte[] actualMac = hmac.doFinal(response); return MessageDigest.isEqual(expectedMac, actualMac); } public byte[] encryptData(byte[] data, PublicKey key) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(data); } public byte[] decryptData(byte[] encryptedData) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, cryptoKeyPair.getPrivate()); return cipher.doFinal(encryptedData); } public static class SecureNFCSession { private final byte[] sessionKey; private final long sessionStart; private final String sessionId; public SecureNFCSession(byte[] sessionKey) { this.sessionKey = sessionKey.clone(); this.sessionStart = System.currentTimeMillis(); this.sessionId = generateSessionId(); } public byte[] getSessionKey() { return sessionKey.clone(); } public boolean isValid() { // Session expires after 5 minutes return System.currentTimeMillis() - sessionStart < 300000; } private String generateSessionId() { return "SESS_" + System.currentTimeMillis() + "_" + new Random().nextInt(10000); } } }

Testing and Debugging

Example 5: NFC Test Suite

import org.junit.*; import static org.junit.Assert.*; public class NFCTestSuite { private AdvancedNFCManager nfcManager; @Before public void setUp() { nfcManager = new AdvancedNFCManager(); } @Test public void testTerminalDiscovery() { List<String> terminals = nfcManager.getTerminalNames(); assertNotNull("Terminal list should not be null", terminals); // Note: Test will fail if no NFC reader is connected } @Test public void testNDEFRecordCreation() { NDEFProcessor.NDEFRecord textRecord = NDEFProcessor.NDEFRecord.createTextRecord("Test", "en"); assertNotNull("Text record should be created", textRecord); assertEquals("Text should match", "Test", textRecord.getText()); NDEFProcessor.NDEFRecord uriRecord = NDEFProcessor.NDEFRecord.createUriRecord("https://test.com"); assertNotNull("URI record should be created", uriRecord); assertEquals("URI should match", "https://test.com", uriRecord.getUri()); } @Test public void testNDEFMessageEncoding() { NDEFProcessor.NDEFMessage message = new NDEFProcessor.NDEFMessage(); message.addRecord(NDEFProcessor.NDEFRecord.createTextRecord("Hello", "en")); byte[] encoded = message.toBytes(); assertNotNull("Encoded message should not be null", encoded); assertTrue("Encoded message should not be empty", encoded.length > 0); NDEFProcessor.NDEFMessage decoded = NDEFProcessor.NDEFMessage.fromBytes(encoded); assertNotNull("Decoded message should not be null", decoded); assertEquals("Should have one record", 1, decoded.getRecords().size()); } @After public void tearDown() { if (nfcManager != null) { nfcManager.stopMonitoring(); } } }

Conclusion

Key Features Implemented

  1. Basic NFC Reading: Card detection and communication
  2. Advanced Card Handling: Support for multiple card types
  3. NDEF Processing: Standard NFC data format support
  4. Security: Secure operations and authentication
  5. Event Management: Asynchronous card detection
  6. Cross-Platform Support: Windows, Linux, macOS

Common Use Cases

  • Access Control: Employee badges, building access
  • Payment Systems: Contactless payments
  • Inventory Management: Product tracking with NFC tags
  • Smart Posters: Interactive advertising
  • Data Exchange: Contactless file sharing
  • Authentication: Two-factor authentication

Best Practices

  1. Error Handling: Robust exception handling for card operations
  2. Resource Management: Proper connection cleanup
  3. Security: Implement authentication and encryption
  4. Performance: Asynchronous operations for better responsiveness
  5. Compatibility: Support for multiple card types and standards

This comprehensive NFC implementation provides a solid foundation for building Java applications that interact with NFC tags and readers across different platforms.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper