- JAVA
Java UUIDの生成と変換|randomUUID・ハイフンなし
JavaでUUID(重複しにくい128ビットの識別子)を作るには、UUID.randomUUID() を呼ぶだけです。 toString() でハイフンつき36文字の文字列になり、replace("-", "") でハイフンなし32文字になります。追加のライブラリは不要で、java.util.UUID だけで完結します。
この記事では、生成・文字列との変換・ハイフンなしからの逆変換・fromString のエラー文・nameUUIDFromBytes・version() と variant()・byte 配列との相互変換を、すべてコピーして動くコードと実行結果のセットで紹介します。冒頭の早見表だけで、やりたいことに対応するメソッドがわかるようにしました。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10(java ファイル名.java で実行)。名前ベースの UUID の照合には Python 3.11.15 を使っています。
UUIDの早見表
やりたいこと別のメソッド
| やりたいこと | 書き方 | 結果の例 |
|---|---|---|
| ランダムな UUID を作る | UUID.randomUUID() |
49166731-f6ac-4d35-b9bd-3d715737826c |
| 文字列にする(36文字) | id.toString() |
同上 |
| ハイフンなし32文字にする | id.toString().replace("-", "") |
49166731f6ac4d35b9bd3d715737826c |
| 大文字にする | id.toString().toUpperCase() |
49166731-F6AC-4D35-B9BD-3D715737826C |
| 文字列から UUID に戻す | UUID.fromString(文字列) |
大文字でも読める |
| ハイフンなしから UUID に戻す | ハイフンを差し込んでから fromString(この記事の fromCompact) |
― |
| 同じ入力から同じ UUID を作る | UUID.nameUUIDFromBytes(バイト配列) |
入力が同じなら毎回同じ値 |
| バージョンを調べる | id.version() |
randomUUID は 4、nameUUIDFromBytes は 3 |
| バリアントを調べる | id.variant() |
2 |
| byte[16] にする | ByteBuffer に上位・下位の long を入れる(この記事の toBytes) |
16バイト |
| 上位・下位64ビットを取り出す | id.getMostSignificantBits() / id.getLeastSignificantBits() |
long の値 |
| 2つの UUID が等しいか調べる | a.equals(b) |
true / false |
| 並べ替える | list.sort(Comparator.naturalOrder()) |
文字列順とは違う並びになることがある |
| 形が正しいか調べる | 正規表現で 8-4-4-4-12 桁を確認(この記事の isUuid) |
true / false |
UUID の形
| 項目 | 内容 |
|---|---|
| 大きさ | 128ビット(16バイト) |
| 文字列の形 | 16進数を 8-4-4-4-12 桁に区切った36文字(ハイフン4つを含む) |
| ハイフンなし | 32文字 |
| バージョンの位置 | 3つ目のかたまりの先頭1文字(randomUUID なら 4) |
| バリアントの位置 | 4つ目のかたまりの先頭1文字(8・9・a・b のどれか) |
| import | java.util.UUID |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java UUIDの生成と変換|randomUUID・ハイフンなし」
randomUUID で UUID を生成する
まずは生成して文字列にするまでの最小のコードです。UuidBasic.java として保存し、java UuidBasic.java で実行します。
import java.util.UUID;
public class UuidBasic {
public static void main(String[] args) {
// 生成(呼ぶたびに違う値になる)
UUID id = UUID.randomUUID();
System.out.println("UUID : " + id);
// 文字列にする(ハイフンつき36文字)
String text = id.toString();
System.out.println("文字数 : " + text.length());
// ハイフンなし32文字にする
String compact = text.replace("-", "");
System.out.println("ハイフンなし: " + compact + "(" + compact.length() + "文字)");
// 大文字にする
System.out.println("大文字 : " + text.toUpperCase());
// 続けて3つ生成
for (int i = 0; i < 3; i++) {
System.out.println(UUID.randomUUID());
}
}
}
実行結果です(ランダムな値なので、実行するたびに変わります)。
UUID : 49166731-f6ac-4d35-b9bd-3d715737826c
文字数 : 36
ハイフンなし: 49166731f6ac4d35b9bd3d715737826c(32文字)
大文字 : 49166731-F6AC-4D35-B9BD-3D715737826C
152a5ec6-0854-4cb8-bbf4-eb430bfa291f
b13d4803-781b-4f71-b032-c6d0ee9ae7a2
bff317e0-bbc1-4d0d-98bf-17fdd7fbe2b8
toString()は必ず小文字・ハイフンつきの36文字を返します。- ハイフンは4つなので、取り除くと32文字です。
- 続けて生成した3つは、すべて違う値になっています。3つ目のかたまりの先頭は、どれも 4 です。
Java 21 の API ドキュメントには、randomUUID() は「タイプ4(疑似乱数で生成)の UUID を返し、暗号論的に強い疑似乱数生成器を使う」と書かれています。128ビットのうち、バージョンの4ビットとバリアントの2ビットが固定で、残りの 122ビットがランダムです。
ハイフンなし32文字への変換と逆変換
ハイフンなしにするのは replace の1行ですが、ハイフンなしの文字列は UUID.fromString でそのまま読めません(次の章でエラー文を確かめます)。逆変換には、ハイフンを差し込む方法と、上位・下位64ビットから組み立てる方法があります。
import java.util.UUID;
public class UuidConvert {
/** UUID → ハイフンなし32文字 */
static String toCompact(UUID id) {
return id.toString().replace("-", "");
}
/** ハイフンなし32文字 → UUID(正規表現でハイフンを差し込む) */
static UUID fromCompact(String s) {
if (s == null || !s.matches("[0-9a-fA-F]{32}")) {
throw new IllegalArgumentException("32桁の16進数ではありません: " + s);
}
String withHyphen = s.replaceFirst(
"(.{8})(.{4})(.{4})(.{4})(.{12})", "$1-$2-$3-$4-$5");
return UUID.fromString(withHyphen);
}
/** ハイフンなし32文字 → UUID(上位64ビットと下位64ビットから作る) */
static UUID fromCompactBits(String s) {
long most = Long.parseUnsignedLong(s.substring(0, 16), 16);
long least = Long.parseUnsignedLong(s.substring(16, 32), 16);
return new UUID(most, least);
}
public static void main(String[] args) {
UUID id = UUID.fromString("3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90");
String compact = toCompact(id);
System.out.println("元の値 : " + id);
System.out.println("ハイフンなし : " + compact);
System.out.println("逆変換(正規表現): " + fromCompact(compact));
System.out.println("逆変換(ビット) : " + fromCompactBits(compact));
System.out.println("元と等しい : " + id.equals(fromCompact(compact)));
System.out.println("大文字も読める : " + fromCompact(compact.toUpperCase()));
try {
fromCompact("3f2b8c1e9a4d");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
実行結果です。
元の値 : 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
ハイフンなし : 3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90
逆変換(正規表現): 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
逆変換(ビット) : 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
元と等しい : true
大文字も読める : 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
32桁の16進数ではありません: 3f2b8c1e9a4d
| 方法 | 仕組み | 向いている場面 |
|---|---|---|
fromCompact(正規表現) |
8・4・4・4・12文字に分けてハイフンを入れ、fromString に渡す |
読みやすさを優先するとき。入力の形も同時に確かめられる |
fromCompactBits(ビット) |
前半16文字と後半16文字を Long.parseUnsignedLong で long にし、new UUID(上位, 下位) |
大量に変換するとき |
ポイントは Long.parseUnsignedLong を使うことです。UUID の前半が 8〜f で始まると、符号つきの Long.parseLong では範囲を超えて NumberFormatException になります。符号なしとして読む parseUnsignedLong なら、どの値でも変換できます。
fromString で文字列から UUID に戻す
読める文字列と、エラーになる文字列
UUID.fromString にいろいろな文字列を渡して、結果とエラー文を一覧にします。
import java.util.UUID;
public class UuidParseErrors {
public static void main(String[] args) {
String[] inputs = {
"3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90", // 正しい形
"3F2B8C1E-9A4D-4E7B-B6F0-5D1C2A7E8F90", // 大文字
"3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90", // ハイフンなし
"", // 空文字
"abc", // 短い文字列
"3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f9g", // 16進数ではない文字 g
"3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90-00", // 区切りが多い
"3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90123456", // 長すぎる
"{3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90}", // 波かっこつき
" 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90", // 先頭に空白
"1-2-3-4-5", // 桁数が足りない
};
for (String in : inputs) {
try {
System.out.println("[" + in + "] -> " + UUID.fromString(in));
} catch (IllegalArgumentException e) {
System.out.println("[" + in + "] -> " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
try {
UUID.fromString(null);
} catch (NullPointerException e) {
System.out.println("[null] -> NullPointerException");
}
}
}
実行結果です。
[3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90] -> 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
[3F2B8C1E-9A4D-4E7B-B6F0-5D1C2A7E8F90] -> 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
[3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90] -> IllegalArgumentException: Invalid UUID string: 3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90
[] -> IllegalArgumentException: Invalid UUID string:
[abc] -> IllegalArgumentException: Invalid UUID string: abc
[3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f9g] -> NumberFormatException: Error at index 11 in: "5d1c2a7e8f9g"
[3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90-00] -> IllegalArgumentException: UUID string too large
[3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90123456] -> IllegalArgumentException: UUID string too large
[{3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90}] -> IllegalArgumentException: UUID string too large
[ 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90] -> IllegalArgumentException: UUID string too large
[1-2-3-4-5] -> 00000001-0002-0003-0004-000000000005
[null] -> NullPointerException
| 入力 | 結果 | エラー文 |
|---|---|---|
| 正しい形(小文字・大文字) | 読める(小文字にそろう) | ― |
| ハイフンなし32文字 | 例外 | Invalid UUID string: 3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90 |
| 空文字、短い文字列 | 例外 | Invalid UUID string:(後ろに入力した文字列が続く) |
| 16進数ではない文字を含む | 例外(NumberFormatException) |
Error at index 11 in: "5d1c2a7e8f9g" |
| 36文字より長い(波かっこつき、空白つきを含む) | 例外 | UUID string too large |
1-2-3-4-5 |
読める | 00000001-0002-0003-0004-000000000005 になる |
| null | NullPointerException |
― |
気をつけたい点が2つあります。
NumberFormatExceptionはIllegalArgumentExceptionの子クラスです。catch (IllegalArgumentException e)と書いておけば、どのパターンもまとめて受け取れます。1-2-3-4-5のように桁数が足りない文字列も、ハイフンが4つあれば読めてしまいます。 足りない桁は 0 で埋められます。入力の形をきちんと確かめたいときは、後で紹介する正規表現のチェックを先に通します。
例外をそのまま出したときのスタックトレース
ハイフンなしの文字列を、try-catch なしで fromString に渡した場合です。
import java.util.UUID;
public class UuidTrace {
public static void main(String[] args) {
UUID id = UUID.fromString("3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90");
System.out.println(id);
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Invalid UUID string: 3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90
at java.base/java.util.UUID.fromString1(UUID.java:282)
at java.base/java.util.UUID.fromString(UUID.java:260)
at UuidTrace.main(UuidTrace.java:5)
Invalid UUID string: の後ろに渡した文字列がそのまま出るので、ログを見れば何が渡されたかがわかります。
形を確かめてから変換する
外から届いた文字列(画面の入力、URL の一部、ファイルの値など)を UUID にするときは、8-4-4-4-12 桁の形を先に確かめると安心です。
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Pattern;
public class UuidStrict {
private static final Pattern UUID_PATTERN = Pattern.compile(
"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
/** 8-4-4-4-12 桁の形かどうかを調べる */
static boolean isUuid(String s) {
return s != null && UUID_PATTERN.matcher(s).matches();
}
/** 形が合うときだけ UUID にする */
static Optional<UUID> tryParse(String s) {
return isUuid(s) ? Optional.of(UUID.fromString(s)) : Optional.empty();
}
public static void main(String[] args) {
System.out.println(isUuid("3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90"));
System.out.println(isUuid("1-2-3-4-5"));
System.out.println(isUuid("3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90"));
System.out.println(tryParse("3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90"));
System.out.println(tryParse("1-2-3-4-5"));
System.out.println(tryParse(null));
}
}
true
false
false
Optional[3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90]
Optional.empty
Optional.empty
tryParse は、形が合わないときに例外ではなく空の Optional を返します。呼び出し側は orElse や ifPresent で次の処理を選べます。
nameUUIDFromBytes で同じ入力から同じ UUID を作る
randomUUID は呼ぶたびに違う値になります。一方、nameUUIDFromBytes は入力のバイト配列から計算するので、同じ入力なら何度呼んでも同じ UUID になります。 会員番号や備品番号など、すでにある値から決まった UUID を作りたいときに使えます。
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.UUID;
public class UuidName {
/** 名前空間つきのバージョン3(MD5)。Python の uuid.uuid3 と同じ値になる */
static UUID uuid3(UUID namespace, String name) {
byte[] n = name.getBytes(StandardCharsets.UTF_8);
ByteBuffer buf = ByteBuffer.allocate(16 + n.length);
buf.putLong(namespace.getMostSignificantBits());
buf.putLong(namespace.getLeastSignificantBits());
buf.put(n);
return UUID.nameUUIDFromBytes(buf.array());
}
/** 名前空間つきのバージョン5(SHA-1)。標準 API にはないので自分で組み立てる */
static UUID uuid5(UUID namespace, String name) throws Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
ByteBuffer ns = ByteBuffer.allocate(16);
ns.putLong(namespace.getMostSignificantBits());
ns.putLong(namespace.getLeastSignificantBits());
sha1.update(ns.array());
byte[] hash = sha1.digest(name.getBytes(StandardCharsets.UTF_8));
hash[6] = (byte) ((hash[6] & 0x0f) | 0x50); // バージョン 5
hash[8] = (byte) ((hash[8] & 0x3f) | 0x80); // バリアント 2
ByteBuffer bb = ByteBuffer.wrap(hash, 0, 16);
return new UUID(bb.getLong(), bb.getLong());
}
public static void main(String[] args) throws Exception {
// 同じ入力からは、何度呼んでも同じ UUID になる
UUID a = UUID.nameUUIDFromBytes("member-1001".getBytes(StandardCharsets.UTF_8));
UUID b = UUID.nameUUIDFromBytes("member-1001".getBytes(StandardCharsets.UTF_8));
UUID c = UUID.nameUUIDFromBytes("member-1002".getBytes(StandardCharsets.UTF_8));
System.out.println("1回目 : " + a);
System.out.println("2回目 : " + b);
System.out.println("等しい : " + a.equals(b));
System.out.println("入力を1文字変更: " + c);
System.out.println("version : " + a.version());
// 文字コードが違うと、日本語では別の値になる
System.out.println("UTF-8 : " + UUID.nameUUIDFromBytes("会員".getBytes(StandardCharsets.UTF_8)));
System.out.println("UTF-16 : " + UUID.nameUUIDFromBytes("会員".getBytes(StandardCharsets.UTF_16)));
// 名前空間つき(DNS 用の名前空間 + example.com)
UUID dns = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
System.out.println("uuid3 : " + uuid3(dns, "example.com"));
System.out.println("uuid5 : " + uuid5(dns, "example.com") + " (version " + uuid5(dns, "example.com").version() + ")");
}
}
実行結果です。
1回目 : 053a6f7e-5d21-34f7-aa6d-1b8387df1c6b
2回目 : 053a6f7e-5d21-34f7-aa6d-1b8387df1c6b
等しい : true
入力を1文字変更: 0db810bc-780e-3b37-8dd4-416e43163aae
version : 3
UTF-8 : c7f8bf0a-b022-31c7-88e0-5ecdf2f879c3
UTF-16 : ba1b7322-f14d-33e7-88f2-fef91178666b
uuid3 : 9073926b-929f-31c2-abc9-fad77ae3e8eb
uuid5 : cfbff0d1-9375-5685-968c-48ce8b15ae17 (version 5)
- 1回目と2回目は同じ値で、
equalsもtrueです。 - 入力を1文字変えると(1001 → 1002)、まったく違う値になります。
version()は 3 です。API ドキュメントにも「タイプ3(名前ベース)の UUID を返す」と書かれています。- 日本語を含む入力は、文字コードが違うとバイト配列が変わり、別の UUID になります。
getBytes(StandardCharsets.UTF_8)のように、文字コードを必ず指定します。
ほかの言語と値をそろえる(名前空間つき)
Java の nameUUIDFromBytes は、渡したバイト配列をそのまま MD5 にかけます。Python の uuid.uuid3 などは「名前空間の UUID の16バイト+名前」を MD5 にかける形なので、同じ値にしたいときは名前空間の16バイトを前に付けてから渡します(上のコードの uuid3 メソッド)。Python 3.11.15 で照合しました。
import uuid, hashlib
print("uuid3 :", uuid.uuid3(uuid.NAMESPACE_DNS, "example.com"))
print("uuid5 :", uuid.uuid5(uuid.NAMESPACE_DNS, "example.com"))
print("md5 :", uuid.UUID(bytes=hashlib.md5(b"member-1001").digest(), version=3))
uuid3 : 9073926b-929f-31c2-abc9-fad77ae3e8eb
uuid5 : cfbff0d1-9375-5685-968c-48ce8b15ae17
md5 : 053a6f7e-5d21-34f7-aa6d-1b8387df1c6b
| 比べたもの | Java の結果 | Python の結果 |
|---|---|---|
| 名前空間つきバージョン3 | 9073926b-929f-31c2-abc9-fad77ae3e8eb |
同じ |
| 名前空間つきバージョン5 | cfbff0d1-9375-5685-968c-48ce8b15ae17 |
同じ |
member-1001 の MD5 から作った値 |
053a6f7e-5d21-34f7-aa6d-1b8387df1c6b |
同じ |
バージョン5(SHA-1 を使う名前ベース)を作るメソッドは Java 21 の UUID クラスにはないため、MessageDigest で計算し、バージョンとバリアントのビットを自分で入れています。
version と variant で UUID の種類を見分ける
version() は UUID の作り方の種類、variant() はビットの並べ方の種類を返します。いくつかの UUID で出力を確かめます。
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
public class UuidInfo {
public static void main(String[] args) {
Map<String, UUID> samples = new LinkedHashMap<>();
samples.put("randomUUID", UUID.randomUUID());
samples.put("nameUUIDFromBytes", UUID.nameUUIDFromBytes("a".getBytes(StandardCharsets.UTF_8)));
samples.put("時刻ベースの例", UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"));
samples.put("バージョン7の例", UUID.fromString("0199a1b2-c3d4-7e5f-8a6b-1c2d3e4f5a6b"));
samples.put("すべて0", new UUID(0L, 0L));
for (Map.Entry<String, UUID> e : samples.entrySet()) {
UUID u = e.getValue();
System.out.println(e.getKey() + ": " + u
+ " version=" + u.version() + " variant=" + u.variant());
}
// バージョンは3つ目のかたまりの先頭1文字に現れる
String s = samples.get("randomUUID").toString();
System.out.println("3つ目のかたまりの先頭: " + s.charAt(14));
System.out.println("4つ目のかたまりの先頭: " + s.charAt(19));
// timestamp() はバージョン1専用
UUID v1 = samples.get("時刻ベースの例");
long t = v1.timestamp(); // 1582-10-15 からの 100 ナノ秒単位
long epochMillis = (t - 0x01B21DD213814000L) / 10_000;
System.out.println("v1 の時刻: " + Instant.ofEpochMilli(epochMillis));
try {
samples.get("randomUUID").timestamp();
} catch (UnsupportedOperationException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
// バージョン7は先頭48ビットがミリ秒の時刻
UUID v7 = samples.get("バージョン7の例");
System.out.println("v7 の時刻: " + Instant.ofEpochMilli(v7.getMostSignificantBits() >>> 16));
}
}
実行結果です。
randomUUID: 935e1041-b96e-4701-b41f-fc5cb8178623 version=4 variant=2
nameUUIDFromBytes: 0cc175b9-c0f1-36a8-b1c3-99e269772661 version=3 variant=2
時刻ベースの例: 6ba7b810-9dad-11d1-80b4-00c04fd430c8 version=1 variant=2
バージョン7の例: 0199a1b2-c3d4-7e5f-8a6b-1c2d3e4f5a6b version=7 variant=2
すべて0: 00000000-0000-0000-0000-000000000000 version=0 variant=0
3つ目のかたまりの先頭: 4
4つ目のかたまりの先頭: b
v1 の時刻: 1998-02-04T22:13:53.151Z
UnsupportedOperationException: Not a time-based UUID
v7 の時刻: 2025-10-01T21:34:31.380Z
「バージョン7の例」は、バージョン7の形をした文字列を fromString で読み込んだものです。Java 21 の fromString と version() は、バージョン7の文字列もそのまま扱えます。
| version() の値 | 種類 | Java 21 で作る方法 |
|---|---|---|
| 1 | 時刻ベース | 生成メソッドはない(読み込みと timestamp() などの参照は可能) |
| 3 | 名前ベース(MD5) | UUID.nameUUIDFromBytes |
| 4 | ランダム | UUID.randomUUID |
| 5 | 名前ベース(SHA-1) | 生成メソッドはない(この記事の uuid5 のように組み立てる) |
| 7 | 時刻(ミリ秒)+ランダム | 生成メソッドはない(読み込みは可能) |
variant()が 2 なら、標準的な UUID の並びです。API ドキュメントでは、0 は NCS との互換用、2 は IETF RFC 4122(Leach-Salz)、6 は Microsoft との互換用、7 は将来のための予約と説明されています。- すべて 0 の UUID は、
version()もvariant()も 0 になります。 timestamp()・clockSequence()・node()はバージョン1専用です。それ以外の UUID で呼ぶとUnsupportedOperationException: Not a time-based UUIDになります。- バージョン7は先頭48ビットがミリ秒単位の時刻なので、
getMostSignificantBits() >>> 16で取り出せます。
バージョン7の生成については、Java SE 26 の API ドキュメントに UUID.ofEpochMillis(long timestamp)(Since 26)が載っています。「指定した時刻を先頭6バイトに入れ、残りを暗号論的に強い疑似乱数で埋めたタイプ7の UUID を作る」と説明されており、クラスの説明も RFC 9562 を参照する形に変わっています。
byte[16] と相互に変換する
UUID の中身は 64ビットの long が2つです。ByteBuffer に順に入れれば16バイトの配列になり、逆も同じ要領で戻せます。
import java.nio.ByteBuffer;
import java.util.HexFormat;
import java.util.UUID;
public class UuidBytes {
/** UUID → byte[16] */
static byte[] toBytes(UUID id) {
return ByteBuffer.allocate(16)
.putLong(id.getMostSignificantBits())
.putLong(id.getLeastSignificantBits())
.array();
}
/** byte[16] → UUID */
static UUID fromBytes(byte[] bytes) {
if (bytes == null || bytes.length != 16) {
throw new IllegalArgumentException("16バイトではありません");
}
ByteBuffer buf = ByteBuffer.wrap(bytes);
return new UUID(buf.getLong(), buf.getLong());
}
public static void main(String[] args) {
UUID id = UUID.fromString("3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90");
byte[] bytes = toBytes(id);
System.out.println("長さ : " + bytes.length);
System.out.println("16進数 : " + HexFormat.of().formatHex(bytes));
System.out.println("逆変換 : " + fromBytes(bytes));
System.out.println("等しい : " + id.equals(fromBytes(bytes)));
System.out.println("文字列のバイト数: " + id.toString().getBytes().length);
try {
fromBytes(new byte[8]);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
長さ : 16
16進数 : 3f2b8c1e9a4d4e7bb6f05d1c2a7e8f90
逆変換 : 3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90
等しい : true
文字列のバイト数: 36
16バイトではありません
- 16バイトを16進数で表示すると、ハイフンなし32文字と同じ並びになります。
- 文字列のままだと36バイト、byte 配列なら16バイトです。数が多いデータをファイルや通信で扱うときに、大きさを抑えられます。
比較・Map のキー・並べ替え
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class UuidCompare {
public static void main(String[] args) {
UUID a = UUID.fromString("3f2b8c1e-9a4d-4e7b-b6f0-5d1c2a7e8f90");
UUID b = UUID.fromString("3F2B8C1E-9A4D-4E7B-B6F0-5D1C2A7E8F90");
// 比較は equals(== は別のオブジェクトなので false)
System.out.println("equals : " + a.equals(b));
System.out.println("== : " + (a == b));
System.out.println("文字列どうしの equals: " + a.toString().equals("3F2B8C1E-9A4D-4E7B-B6F0-5D1C2A7E8F90"));
// Map のキーにする
Map<UUID, String> names = new HashMap<>();
names.put(a, "備品A");
System.out.println("Map から取得: " + names.get(b));
// 並べ替え:compareTo と、文字列順の違い
List<UUID> list = new ArrayList<>(List.of(
UUID.fromString("80000000-0000-4000-8000-000000000001"),
UUID.fromString("00000000-0000-4000-8000-000000000002"),
UUID.fromString("7fffffff-0000-4000-8000-000000000003")));
list.sort(Comparator.naturalOrder());
System.out.println("compareTo 順: " + list);
list.sort(Comparator.comparing(UUID::toString));
System.out.println("文字列順 : " + list);
}
}
equals : true
== : false
文字列どうしの equals: false
Map から取得: 備品A
compareTo 順: [80000000-0000-4000-8000-000000000001, 00000000-0000-4000-8000-000000000002, 7fffffff-0000-4000-8000-000000000003]
文字列順 : [00000000-0000-4000-8000-000000000002, 7fffffff-0000-4000-8000-000000000003, 80000000-0000-4000-8000-000000000001]
- UUID どうしの比較は
equalsを使います。大文字の文字列から作っても、中身が同じならtrueです。 - 文字列のまま比べると、大文字と小文字の違いで
falseになります。文字列ではなく UUID 型にしてから比べると、表記のゆれを気にせずに済みます。 equalsとhashCodeがそろっているので、HashMapのキーにそのまま使えます。compareToの順番は文字列順と同じとは限りません。 上位・下位の64ビットを符号つきの long として比べるため、8〜f で始まる UUID が 0〜7 で始まるものより前に来ます。画面に出す順番を文字列順にそろえたいときは、Comparator.comparing(UUID::toString)を使います。
つまずきやすい点と直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
Invalid UUID string: ... |
ハイフンなし、空文字など、ハイフン4つの形になっていない | ハイフンなしは fromCompact で変換する。空文字は先に判定する |
UUID string too large |
36文字より長い(前後の空白、波かっこ、改行など) | strip() で空白を取り、余分な記号を外してから渡す |
NumberFormatException: Error at index ... |
16進数ではない文字が混ざっている | 正規表現で形を確かめてから変換する |
| 桁数の足りない文字列が読めてしまう | fromString はハイフン4つで区切れれば受け付ける |
isUuid のように 8-4-4-4-12 桁を確認する |
Not a time-based UUID |
バージョン1以外で timestamp() などを呼んだ |
version() == 1 を確かめてから呼ぶ |
nameUUIDFromBytes の値がほかの言語と合わない |
名前空間の16バイトを付けていない、または文字コードが違う | 名前空間+名前のバイト配列を渡す。文字コードは UTF-8 にそろえる |
| 並べ替えの結果が思った順にならない | compareTo は符号つきの long で比べる |
文字列順にしたいときは Comparator.comparing(UUID::toString) |
| 大文字の文字列と一致しない | 文字列どうしで比べている | UUID.fromString で UUID にしてから equals |
どの作り方を選ぶかの判断表
| 目的 | 選ぶ方法 |
|---|---|
| とにかく重複しにくい ID がほしい | UUID.randomUUID() |
| 同じ入力から、いつも同じ ID を作りたい | UUID.nameUUIDFromBytes(...) |
| ほかの言語の名前ベース UUID と値を合わせたい | 名前空間の16バイト+名前を渡す(uuid3)、または SHA-1 で組み立てる(uuid5) |
| URL やファイル名に短めに入れたい | ハイフンなし32文字 |
| 大きさを抑えて持ちたい | byte[16] |
| 生成した順に並ぶ ID がほしい | バージョン7(Java SE 26 の API ドキュメントに UUID.ofEpochMillis の記載あり) |
よくある質問
Q. randomUUID の値が重複することはありますか? A. 122ビットがランダムに決まるため、同じ値が出る可能性は理論上ゼロではありませんが、とても小さい値です。API ドキュメントによると、乱数には暗号論的に強い疑似乱数生成器が使われます。確実に一意であることが必要な場面では、保存先に一意の制約を付けておくと、万一のときにも気づけます。
Q. ハイフンなしの文字列を fromString に渡せますか?
A. Java 21 では IllegalArgumentException: Invalid UUID string: ... になります。8・4・4・4・12文字に分けてハイフンを入れるか、前半と後半の16文字を Long.parseUnsignedLong(..., 16) で読み、new UUID(上位, 下位) で作ります。
Q. nameUUIDFromBytes と randomUUID はどう使い分けますか?
A. 毎回違う値がほしいなら randomUUID、同じ入力から同じ値を再現したいなら nameUUIDFromBytes です。後者は入力がわかれば誰でも同じ値を計算できるので、推測されたくない値(パスワードの再設定用の文字列など)には randomUUID を使います。
Q. UUID は大文字と小文字のどちらで保存するのがよいですか?
A. toString() は小文字を返すので、小文字にそろえると扱いやすくなります。fromString は大文字も読めるため、読み込む側は UUID 型に変換してから比べれば、表記の違いを気にせずに済みます。
まとめ
- 生成は
UUID.randomUUID()。toString()で小文字・ハイフンつき36文字になる。 - ハイフンなしは
toString().replace("-", "")の32文字。逆変換はハイフンを差し込むか、Long.parseUnsignedLongで上位・下位を読んでnew UUID(...)。 fromStringのエラー文はInvalid UUID string・UUID string too large・Error at index ...の3種類。どれもIllegalArgumentExceptionで受け取れる。1-2-3-4-5のような桁数の足りない文字列も読めてしまうので、形の確認には正規表現を使う。nameUUIDFromBytesは同じ入力から同じ値(バージョン3)。文字コードを指定し、ほかの言語と合わせるときは名前空間の16バイトを付ける。version()で種類、variant()で並べ方がわかる。randomUUIDは version 4・variant 2。- byte[16] との変換は
ByteBuffer、比較はequals、文字列順の並べ替えはComparator.comparing(UUID::toString)。
参考・出典
- Java SE 21 API ドキュメント:java.util.UUID(https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/UUID.html)
- Java SE 26 API ドキュメント:java.util.UUID(https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/UUID.html)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java UUIDの生成と変換|randomUUID・ハイフンなし」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。