Bug Description
The service metadata timestamp is stored as a String representation of System.currentTimeMillis() without timezone information or standard date format, making it difficult to parse, compare, or interpret.
Location
jplatform-registry-etcd/src/main/java/org/flossware/jplatform/registry/etcd/EtcdServiceRegistry.java:104
Problematic Code
Map<String, String> metadata = new HashMap<>();
metadata.put("interface", serviceInterface.getName());
metadata.put("implementation", implementation.getClass().getName());
metadata.put("timestamp", String.valueOf(System.currentTimeMillis())); // Just milliseconds!
String json = mapper.writeValueAsString(metadata);
Impact
- No timezone: Milliseconds since epoch, no indication of timezone
- Not human-readable: "1737907200000" not meaningful to humans
- Hard to parse: Consumers must know it's Unix milliseconds
- No standard format: Not ISO-8601 or any other standard
- Comparison issues: String comparison doesn't work for times
- Sorting problems: Can't sort chronologically without parsing
Example
// Current metadata in etcd:
{
"interface": "com.example.MyService",
"implementation": "com.example.MyServiceImpl",
"timestamp": "1737907200000"
}
// Problems:
// 1. What timezone? (It's UTC but not indicated)
// 2. What does 1737907200000 mean? (Jan 26, 2025 but not obvious)
// 3. How to filter services registered in last hour?
// - Must parse to long, subtract, compare
// 4. How to display to user?
// - Must format programmatically
// When debugging in etcd:
$ etcdctl get /jplatform/services/MyService/abc123
{"timestamp":"1737907200000", ...}
// What time is that?
// Need calculator or code to convert
// When querying recent services:
// Can't do: SELECT * WHERE timestamp > '2025-01-26T00:00:00Z'
// Must do: Parse string, compare longs
Proposed Fix
Option 1 - Use ISO-8601 format:
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
private static final DateTimeFormatter ISO_FORMATTER =
DateTimeFormatter.ISO_INSTANT.withZone(ZoneOffset.UTC);
Map<String, String> metadata = new HashMap<>();
metadata.put("interface", serviceInterface.getName());
metadata.put("implementation", implementation.getClass().getName());
metadata.put("timestamp", ISO_FORMATTER.format(Instant.now()));
// Result: "2025-01-26T12:00:00Z"
String json = mapper.writeValueAsString(metadata);
Option 2 - Store both formats:
Instant now = Instant.now();
Map<String, String> metadata = new HashMap<>();
metadata.put("interface", serviceInterface.getName());
metadata.put("implementation", implementation.getClass().getName());
metadata.put("timestamp", ISO_FORMATTER.format(now)); // Human-readable
metadata.put("timestamp_millis", String.valueOf(now.toEpochMilli())); // For programmatic use
String json = mapper.writeValueAsString(metadata);
Option 3 - Use structured metadata object:
public static class ServiceMetadata {
@JsonProperty("interface")
private String serviceInterface;
@JsonProperty("node_id")
private String nodeId;
@JsonProperty("registered_at")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private Instant registeredAt;
public ServiceMetadata(String serviceInterface, String nodeId) {
this.serviceInterface = serviceInterface;
this.nodeId = nodeId;
this.registeredAt = Instant.now();
}
// Getters
}
// In registerService():
ServiceMetadata metadata = new ServiceMetadata(
serviceInterface.getName(),
getNodeId()
);
String json = mapper.writeValueAsString(metadata);
// Result: {"interface":"...","node_id":"...","registered_at":"2025-01-26T12:00:00.000Z"}
Option 4 - Configure ObjectMapper to handle dates properly:
public EtcdServiceRegistry(EtcdRegistryConfig config) {
this.config = config;
this.localServices = new ConcurrentHashMap<>();
this.registeredServices = new ConcurrentHashMap<>();
// Configure ObjectMapper for proper date handling
this.mapper = new ObjectMapper();
this.mapper.registerModule(new JavaTimeModule());
this.mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
this.mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
}
// Then use Instant in metadata:
Map<String, Object> metadata = new HashMap<>(); // Object, not String
metadata.put("interface", serviceInterface.getName());
metadata.put("timestamp", Instant.now()); // Will be serialized as ISO-8601
String json = mapper.writeValueAsString(metadata);
Recommendation: Option 3 - use structured metadata object with proper date field. This is type-safe, standard, and human-readable.
Bug Description
The service metadata timestamp is stored as a String representation of
System.currentTimeMillis()without timezone information or standard date format, making it difficult to parse, compare, or interpret.Location
jplatform-registry-etcd/src/main/java/org/flossware/jplatform/registry/etcd/EtcdServiceRegistry.java:104Problematic Code
Impact
Example
Proposed Fix
Option 1 - Use ISO-8601 format:
Option 2 - Store both formats:
Option 3 - Use structured metadata object:
Option 4 - Configure ObjectMapper to handle dates properly:
Recommendation: Option 3 - use structured metadata object with proper date field. This is type-safe, standard, and human-readable.