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.