diff --git a/src/java.base/share/classes/java/util/UUID.java b/src/java.base/share/classes/java/util/UUID.java index d1c81badba6..fa66d199239 100644 --- a/src/java.base/share/classes/java/util/UUID.java +++ b/src/java.base/share/classes/java/util/UUID.java @@ -59,21 +59,21 @@ import jdk.internal.util.ByteArrayLittleEndian; * {@code UUID}. The bit layout described above is valid only for a {@code * UUID} with a variant value of 2, which indicates the Leach-Salz variant. * - *
The version field holds a value that describes the type of this {@code - * UUID}. There are four different basic types of UUIDs: time-based, DCE - * security, name-based, and randomly generated UUIDs. These types have a - * version value of 1, 2, 3 and 4, respectively. + *
See + * RFC 9562: Universally Unique Identifiers (UUIDs) for the complete specification, + * including the UUID format, layouts, and algorithms for creating {@code UUID}s. * - *
For more information including algorithms used to create {@code UUID}s, - * see RFC 4122: A - * Universally Unique IDentifier (UUID) URN Namespace, section 4.2 - * "Algorithms for Creating a Time-Based UUID". + *
There are eight defined types of UUIDs, each identified by a version number:
+ * time-based (version 1), DCE security (version 2), name-based with MD5 (version 3),
+ * randomly generated (version 4), name-based with SHA-1 (version 5), reordered time-based (version 6),
+ * Unix epoch time-based (version 7), and custom-defined layout (version 8).
*
- * @spec https://www.rfc-editor.org/info/rfc4122
- * RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace
+ * @spec https://www.rfc-editor.org/rfc/rfc9562.html
+ * RFC 9562 Universally Unique IDentifiers (UUIDs)
* @since 1.5
*/
public final class UUID implements java.io.Serializable, Comparable
+ * Monotonicity (each subsequent value being greater than the last) is a primary characteristic
+ * of {@code UUIDv7} values. This is due to the {@code timestamp} value being part of the {@code UUID}.
+ * Callers of this method that wish to generate monotonic {@code UUIDv7} values are expected to
+ * ensure that the given {@code timestamp} value is monotonic.
+ *
+ *
+ * @param timestamp the number of milliseconds since midnight 1 Jan 1970 UTC,
+ * leap seconds excluded.
+ *
+ * @return a {@code UUID} constructed using the given {@code timestamp}
+ *
+ * @throws IllegalArgumentException if the timestamp is negative or greater than {@code (1L << 48) - 1}
+ *
+ * @since 26
+ */
+ public static UUID ofEpochMillis(long timestamp) {
+ if ((timestamp >> 48) != 0) {
+ throw new IllegalArgumentException("Supplied timestamp: " + timestamp + "does not fit within 48 bits");
+ }
+
+ SecureRandom ng = Holder.numberGenerator;
+ byte[] randomBytes = new byte[16];
+ ng.nextBytes(randomBytes);
+
+ // Embed the timestamp into the first 6 bytes
+ randomBytes[0] = (byte)(timestamp >>> 40);
+ randomBytes[1] = (byte)(timestamp >>> 32);
+ randomBytes[2] = (byte)(timestamp >>> 24);
+ randomBytes[3] = (byte)(timestamp >>> 16);
+ randomBytes[4] = (byte)(timestamp >>> 8);
+ randomBytes[5] = (byte)(timestamp);
+
+ // Set version to 7
+ randomBytes[6] &= 0x0f;
+ randomBytes[6] |= 0x70;
+
+ // Set variant to IETF
+ randomBytes[8] &= 0x3f;
+ randomBytes[8] |= (byte) 0x80;
+
+ return new UUID(randomBytes);
+ }
+
private static final byte[] NIBBLES;
static {
byte[] ns = new byte[256];
@@ -320,6 +376,7 @@ public final class UUID implements java.io.Serializable, Comparable
*
*
* @return The variant number of this {@code UUID}
- *
- * @spec https://www.rfc-editor.org/info/rfc4122
- * RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace
*/
public int variant() {
// This field is composed of a varying number of bits.
diff --git a/test/jdk/java/util/UUID/UUIDTest.java b/test/jdk/java/util/UUID/UUIDTest.java
index 9fbd6dc1788..cb447a05656 100644
--- a/test/jdk/java/util/UUID/UUIDTest.java
+++ b/test/jdk/java/util/UUID/UUIDTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,6 +47,7 @@ public class UUIDTest {
randomUUIDTest();
randomUUIDTest_Multi();
nameUUIDFromBytesTest();
+ testOfEpochMillisTimestamp();
stringTest();
versionTest();
variantTest();
@@ -146,6 +147,44 @@ public class UUIDTest {
}
}
+ private static void testOfEpochMillisTimestamp() {
+ // Should not throw for valid currentTimeMillis() timestamp
+ long timestamp = System.currentTimeMillis();
+ try {
+ UUID u = UUID.ofEpochMillis(timestamp);
+ if (u == null) {
+ throw new AssertionError("Generated UUID should not be null for timestamp: " + timestamp);
+ }
+ } catch (Exception e) {
+ throw new AssertionError("Unexpected exception with timestamp " + timestamp, e);
+ }
+
+ // Should not throw for the 48-bit long
+ long value = 0xFEDCBA987654L;
+ try {
+ UUID u = UUID.ofEpochMillis(value);
+ if (u == null) {
+ throw new AssertionError("Generated UUID should not be null for 48-bit long: " + value);
+ }
+ } catch (Exception e) {
+ throw new AssertionError("Unexpected exception with 48-bit long " + value, e);
+ }
+
+ // Should throw for negative timestamp
+ value = -0xFEDCBA987654L;
+ try {
+ UUID.ofEpochMillis(value);
+ throw new AssertionError("Expected IllegalArgumentException with negative timestamp: " + value);
+ } catch (IllegalArgumentException expected) {}
+
+ // Should throw for timestamp > 48 bits
+ value = 1L << 48;
+ try {
+ UUID.ofEpochMillis(value);
+ throw new AssertionError("Expected IllegalArgumentException with timestamp > 48 bits: " + value);
+ } catch (IllegalArgumentException expected) {}
+ }
+
private static void stringTest() throws Exception {
for (int i = 0; i < COUNT; i++) {
UUID u1 = UUID.randomUUID();
@@ -187,6 +226,15 @@ public class UUIDTest {
throw new Exception("nameUUIDFromBytes not type 3: " + test);
}
+ long timestamp = System.currentTimeMillis();
+ test = UUID.ofEpochMillis(timestamp);
+ if (test.version() != 7) {
+ throw new Exception("ofEpochMillis not type 7: " + test);
+ }
+ if (test.variant() != 2) {
+ throw new Exception("ofEpochMillis not variant 2: " + test);
+ }
+
test = UUID.fromString("9835451d-e2e0-1e41-8a5a-be785f17dcda");
if (test.version() != 1) {
throw new Exception("wrong version fromString 1");