RFID (Radio-Frequency Identification) technology revolutionizes inventory management by enabling automatic, contactless tracking of assets. This guide demonstrates how to build a comprehensive RFID inventory management system using Java.
Why RFID for Inventory Management?
- Automated Tracking: Scan hundreds of items simultaneously without line-of-sight
- Real-time Visibility: Instant inventory updates and location tracking
- Reduced Labor Costs: Eliminate manual counting and barcode scanning
- Improved Accuracy: Near 100% inventory accuracy with automated reads
- Enhanced Security: Track item movement and prevent theft
System Architecture
RFID Tags → RFID Readers → Java Middleware → Database → Web Interface ↓ Mobile/Handheld Scanners
Prerequisites
- RFID Hardware: UHF RFID readers (Impinj, Zebra, Alien) and tags
- Java Environment: JDK 11+ with Maven/Gradle
- Database: PostgreSQL/MySQL for inventory data
- Middleware: LLRP (Low Level Reader Protocol) library
Step 1: Project Dependencies
Maven (pom.xml):
<dependencies> <!-- LLRP for RFID Communication --> <dependency> <groupId>com.impinj</groupId> <artifactId>impinj-java</artifactId> <version>1.0.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.0</version> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.5.0</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> <!-- WebSocket for Real-time Updates --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.7.0</version> </dependency> <!-- Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.7.0</version> </dependency> </dependencies>
Step 2: Entity Models
@Entity @Table(name = "rfid_tags") public class RFIDTag { @Id private String epc; // Electronic Product Code @Column(nullable = false) private String tagId; private String tid; // Tag ID private String userMemory; @Enumerated(EnumType.STRING) private TagStatus status = TagStatus.ACTIVE; @Column(nullable = false) private Integer antennaPort; private Double peakRssi; // Signal strength private Double phaseAngle; @Column(nullable = false) private LocalDateTime firstSeen; private LocalDateTime lastSeen; private Integer readCount = 0; @ManyToOne @JoinColumn(name = "item_id") private InventoryItem item; @ManyToOne @JoinColumn(name = "reader_id") private RFIDReader reader; // Constructors, getters, setters } @Entity @Table(name = "inventory_items") public class InventoryItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String sku; @Column(nullable = false) private String name; private String description; private String category; @Column(nullable = false) private Integer quantity; private Integer reorderThreshold; private String location; // Warehouse location @Column(nullable = false) private LocalDateTime createdAt; private LocalDateTime lastUpdated; @OneToMany(mappedBy = "item", cascade = CascadeType.ALL) private List<RFIDTag> rfidTags; @Enumerated(EnumType.STRING) private ItemStatus status = ItemStatus.IN_STOCK; // Constructors, getters, setters } @Entity @Table(name = "rfid_readers") public class RFIDReader { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String readerId; @Column(nullable = false) private String ipAddress; private Integer port = 5084; // Default LLRP port private String location; private String model; @Enumerated(EnumType.STRING) private ReaderStatus status = ReaderStatus.OFFLINE; private LocalDateTime lastHeartbeat; @OneToMany(mappedBy = "reader") private List<RFIDTag> detectedTags; // Constructors, getters, setters } @Entity @Table(name = "inventory_events") public class InventoryEvent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String epc; @Enumerated(EnumType.STRING) private EventType eventType; private String readerId; private String location; private Double rssi; private Integer antennaPort; @Column(nullable = false) private LocalDateTime timestamp; @Lob private String additionalData; // Constructors, getters, setters } // Enums public enum TagStatus { ACTIVE, INACTIVE, LOST, DAMAGED } public enum ItemStatus { IN_STOCK, LOW_STOCK, OUT_OF_STOCK, RESERVED, SHIPPED } public enum ReaderStatus { ONLINE, OFFLINE, MAINTENANCE, ERROR } public enum EventType { TAG_READ, TAG_ENTER, TAG_EXIT, INVENTORY_UPDATE, ALERT } Step 3: RFID Reader Service
@Service @Slf4j public class RFIDReaderService { @Autowired private RFIDTagRepository tagRepository; @Autowired private InventoryEventRepository eventRepository; @Autowired private RFIDReaderRepository readerRepository; @Autowired private SimpMessagingTemplate messagingTemplate; private Map<String, ImpinjReader> connectedReaders = new ConcurrentHashMap<>(); public void connectToReader(String readerId, String hostname) throws RFIDException { try { ImpinjReader reader = new ImpinjReader(); reader.connect(hostname); // Configure reader settings Settings settings = reader.queryDefaultSettings(); settings.getReport().setMode(ReportMode.Individual); settings.getReport().setIncludePeakRssi(true); settings.getReport().setIncludePhaseAngle(true); settings.getReport().setIncludeFirstSeenTime(true); settings.getReport().setIncludeLastSeenTime(true); // Set antenna configuration settings.getAntennas().disableAll(); settings.getAntennas().enableById(new short[]{1, 2, 3, 4}); settings.getAntennas().setIsMaxRxSensitivity(false); settings.getAntennas().setIsMaxTxPower(false); settings.getAntennas().setTxPowerinDbm(30.0); settings.getAntennas().setRxSensitivityinDbm(-70); reader.applySettings(settings); reader.setTagReportListener(new TagReportListenerImplementation()); connectedReaders.put(readerId, reader); updateReaderStatus(readerId, ReaderStatus.ONLINE); reader.start(); log.info("Connected to RFID reader: {} at {}", readerId, hostname); } catch (Exception e) { throw new RFIDException("Failed to connect to RFID reader: " + hostname, e); } } private class TagReportListenerImplementation implements TagReportListener { @Override public void onTagReported(ImpinjReader reader, TagReport report) { List<Tag> tags = report.getTags(); for (Tag tag : tags) { processTagDetection(tag, reader.getName()); } } } @Async public void processTagDetection(Tag tag, String readerId) { try { String epc = tag.getEpc().toString(); RFIDTag rfidTag = tagRepository.findByEpc(epc) .orElseGet(() -> createNewTag(tag, readerId)); // Update tag information rfidTag.setLastSeen(LocalDateTime.now()); rfidTag.setReadCount(rfidTag.getReadCount() + 1); rfidTag.setPeakRssi(tag.getPeakRssiInDbm()); rfidTag.setPhaseAngle(tag.getPhaseAngleInRadians()); rfidTag.setAntennaPort(tag.getAntennaPortNumber()); tagRepository.save(rfidTag); // Create inventory event InventoryEvent event = new InventoryEvent(); event.setEpc(epc); event.setEventType(EventType.TAG_READ); event.setReaderId(readerId); event.setRssi(tag.getPeakRssiInDbm()); event.setAntennaPort(tag.getAntennaPortNumber()); event.setTimestamp(LocalDateTime.now()); eventRepository.save(event); // Send real-time update via WebSocket sendRealTimeUpdate(rfidTag, event); // Check inventory thresholds checkInventoryLevels(rfidTag.getItem()); } catch (Exception e) { log.error("Error processing tag detection: {}", e.getMessage(), e); } } private RFIDTag createNewTag(Tag tag, String readerId) { RFIDTag rfidTag = new RFIDTag(); rfidTag.setEpc(tag.getEpc().toString()); rfidTag.setTagId(generateTagId()); rfidTag.setTid(tag.getTid() != null ? tag.getTid().toString() : null); rfidTag.setFirstSeen(LocalDateTime.now()); rfidTag.setReader(readerRepository.findByReaderId(readerId) .orElseThrow(() -> new RuntimeException("Reader not found: " + readerId))); log.info("Created new RFID tag: {}", rfidTag.getEpc()); return rfidTag; } private void sendRealTimeUpdate(RFIDTag tag, InventoryEvent event) { Map<String, Object> update = new HashMap<>(); update.put("type", "TAG_DETECTED"); update.put("epc", tag.getEpc()); update.put("readerId", event.getReaderId()); update.put("rssi", event.getRssi()); update.put("timestamp", event.getTimestamp()); update.put("item", tag.getItem()); messagingTemplate.convertAndSend("/topic/inventory-updates", update); } private void checkInventoryLevels(InventoryItem item) { if (item != null && item.getQuantity() <= item.getReorderThreshold()) { // Send low stock alert Map<String, Object> alert = new HashMap<>(); alert.put("type", "LOW_STOCK_ALERT"); alert.put("itemId", item.getId()); alert.put("itemName", item.getName()); alert.put("currentQuantity", item.getQuantity()); alert.put("threshold", item.getReorderThreshold()); messagingTemplate.convertAndSend("/topic/alerts", alert); log.warn("Low stock alert for item: {} - Quantity: {}", item.getName(), item.getQuantity()); } } public void disconnectReader(String readerId) { try { ImpinjReader reader = connectedReaders.get(readerId); if (reader != null) { reader.stop(); reader.disconnect(); connectedReaders.remove(readerId); updateReaderStatus(readerId, ReaderStatus.OFFLINE); log.info("Disconnected RFID reader: {}", readerId); } } catch (Exception e) { log.error("Error disconnecting reader: {}", readerId, e); } } private void updateReaderStatus(String readerId, ReaderStatus status) { readerRepository.findByReaderId(readerId).ifPresent(reader -> { reader.setStatus(status); reader.setLastHeartbeat(LocalDateTime.now()); readerRepository.save(reader); }); } } Step 4: Inventory Management Service
@Service @Transactional public class InventoryService { @Autowired private InventoryItemRepository itemRepository; @Autowired private RFIDTagRepository tagRepository; @Autowired private RFIDReaderService readerService; public InventoryItem addItemToInventory(InventoryItemDTO itemDTO) { InventoryItem item = new InventoryItem(); item.setSku(itemDTO.getSku()); item.setName(itemDTO.getName()); item.setDescription(itemDTO.getDescription()); item.setCategory(itemDTO.getCategory()); item.setQuantity(itemDTO.getQuantity()); item.setReorderThreshold(itemDTO.getReorderThreshold()); item.setLocation(itemDTO.getLocation()); item.setCreatedAt(LocalDateTime.now()); return itemRepository.save(item); } public RFIDTag assignTagToItem(String epc, Long itemId, String readerId) { InventoryItem item = itemRepository.findById(itemId) .orElseThrow(() -> new InventoryException("Item not found: " + itemId)); RFIDTag tag = tagRepository.findByEpc(epc) .orElseThrow(() -> new InventoryException("RFID tag not found: " + epc)); tag.setItem(item); RFIDTag savedTag = tagRepository.save(tag); // Update inventory count based on tag assignments updateItemQuantity(itemId); return savedTag; } public void updateItemQuantity(Long itemId) { InventoryItem item = itemRepository.findById(itemId) .orElseThrow(() -> new InventoryException("Item not found: " + itemId)); long activeTagCount = tagRepository.countByItemIdAndStatus(itemId, TagStatus.ACTIVE); item.setQuantity((int) activeTagCount); item.setLastUpdated(LocalDateTime.now()); // Update item status based on quantity if (activeTagCount == 0) { item.setStatus(ItemStatus.OUT_OF_STOCK); } else if (activeTagCount <= item.getReorderThreshold()) { item.setStatus(ItemStatus.LOW_STOCK); } else { item.setStatus(ItemStatus.IN_STOCK); } itemRepository.save(item); } public InventorySummary getInventorySummary() { InventorySummary summary = new InventorySummary(); summary.setTotalItems(itemRepository.count()); summary.setTotalTags(tagRepository.count()); summary.setLowStockItems(itemRepository.countByStatus(ItemStatus.LOW_STOCK)); summary.setOutOfStockItems(itemRepository.countByStatus(ItemStatus.OUT_OF_STOCK)); summary.setTotalValue(calculateTotalInventoryValue()); return summary; } public List<InventoryItem> findItemsByLocation(String location) { return itemRepository.findByLocationContainingIgnoreCase(location); } public List<RFIDTag> getItemMovementHistory(String epc, LocalDateTime start, LocalDateTime end) { return tagRepository.findByEpcAndLastSeenBetween(epc, start, end); } private BigDecimal calculateTotalInventoryValue() { // Implementation depends on your pricing data return BigDecimal.ZERO; } } Step 5: WebSocket Configuration for Real-time Updates
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-rfid") .setAllowedOriginPatterns("*") .withSockJS(); } } Step 6: REST API Controllers
@RestController @RequestMapping("/api/rfid-inventory") @CrossOrigin(origins = "*") public class RFIDInventoryController { @Autowired private RFIDReaderService readerService; @Autowired private InventoryService inventoryService; @PostMapping("/readers/connect") public ResponseEntity<?> connectReader(@RequestBody ReaderConnectRequest request) { try { readerService.connectToReader(request.getReaderId(), request.getHostname()); return ResponseEntity.ok().build(); } catch (RFIDException e) { return ResponseEntity.badRequest().body( new ErrorResponse("READER_CONNECTION_ERROR", e.getMessage()) ); } } @PostMapping("/items") public ResponseEntity<InventoryItem> createItem(@RequestBody InventoryItemDTO itemDTO) { InventoryItem item = inventoryService.addItemToInventory(itemDTO); return ResponseEntity.ok(item); } @PutMapping("/tags/{epc}/assign/{itemId}") public ResponseEntity<RFIDTag> assignTagToItem( @PathVariable String epc, @PathVariable Long itemId, @RequestParam String readerId) { RFIDTag tag = inventoryService.assignTagToItem(epc, itemId, readerId); return ResponseEntity.ok(tag); } @GetMapping("/inventory/summary") public ResponseEntity<InventorySummary> getInventorySummary() { InventorySummary summary = inventoryService.getInventorySummary(); return ResponseEntity.ok(summary); } @GetMapping("/tags/{epc}/history") public ResponseEntity<List<RFIDTag>> getTagHistory( @PathVariable String epc, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end) { List<RFIDTag> history = inventoryService.getItemMovementHistory(epc, start, end); return ResponseEntity.ok(history); } @GetMapping("/items/search") public ResponseEntity<List<InventoryItem>> searchItems( @RequestParam(required = false) String location, @RequestParam(required = false) String category, @RequestParam(required = false) ItemStatus status) { List<InventoryItem> items = inventoryService.searchItems(location, category, status); return ResponseEntity.ok(items); } } // DTO Classes @Data class ReaderConnectRequest { private String readerId; private String hostname; } @Data class InventoryItemDTO { private String sku; private String name; private String description; private String category; private Integer quantity; private Integer reorderThreshold; private String location; } @Data class InventorySummary { private Long totalItems; private Long totalTags; private Long lowStockItems; private Long outOfStockItems; private BigDecimal totalValue; } Step 7: Configuration and Monitoring
application.yml:
spring: datasource: url: jdbc:postgresql://localhost:5432/rfid_inventory username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:password} jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: false rfid: readers: - readerId: "reader-001" hostname: "192.168.1.100" location: "Warehouse A - Receiving" - readerId: "reader-002" hostname: "192.168.1.101" location: "Warehouse A - Shipping" server: port: 8080 logging: level: com.yourcompany.rfid: DEBUG Key Features Implemented
- Real-time Tag Reading: Continuous monitoring of RFID tags
- Inventory Automation: Automatic quantity updates based on tag presence
- Location Tracking: Track item movement between reader zones
- Alert System: Low stock and anomaly detection
- REST API: Full CRUD operations for inventory management
- WebSocket Updates: Real-time dashboard updates
- Reader Management: Connect/disconnect and monitor RFID readers
- Reporting: Inventory summary and movement history
Best Practices
- Error Handling: Robust exception handling for reader connectivity issues
- Performance: Asynchronous processing of tag events
- Security: Secure reader communication and API authentication
- Scalability: Support for multiple readers and high tag volumes
- Data Integrity: Transactional updates to maintain data consistency
- Monitoring: Health checks and status monitoring for readers
This RFID inventory system provides a solid foundation for automated inventory management that can scale from small warehouses to large distribution centers.