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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?