ترميز Base64 في Java — أمثلة java.util.Base64

·Java Security & API Engineer·مراجعة بواسطةPavel Novak·نُشر

استخدم مشفّر Base64 عبر الإنترنت المجاني مباشرةً في متصفحك — لا حاجة للتثبيت.

جرّب مشفّر Base64 عبر الإنترنت أونلاين ←

في كل مرة أضع فيها رأس HTTP Basic Auth، أو أُدرج شهادة في سر Kubernetes، أو أُمرِّر بيانات ثنائية عبر JSON API، تكون الخطوة الأولى دائماً واحدة: ترميز Base64 للبايتات الخام إلى سلسلة آمنة لـ ASCII. تجعل Java هذا الأمر مباشراً باستخدام java.util.Base64، الواجهة البرمجية القياسية المتاحة منذ Java 8 التي حلَّت محل sun.misc.BASE64Encoder المُهمَلة. للترميز السريع لمرة واحدة دون كتابة أي كود، مُرمِّز Base64 من ToolDeck يُنجزه فوراً في المتصفح. يتناول هذا الدليل Base64.getEncoder()، getUrlEncoder()، getMimeEncoder()، ترميز الملفات، البث باستخدام wrap(OutputStream)، والأخطاء التي تُربك حتى مطوري Java ذوي الخبرة. جميع الأمثلة تُترجَم وتعمل على Java 8 حتى Java 21+.

  • Base64.getEncoder().encodeToString(bytes) هو السطر الواحد القياسي — مدمج في JDK منذ Java 8، ولم يتغير عبر Java 17 و21.
  • مرِّر دائماً StandardCharsets.UTF_8 إلى String.getBytes() قبل الترميز — حذفه يستخدم الافتراضي للمنصة، والذي يتفاوت بين JVMs.
  • يُنتج getUrlEncoder() ناتجاً آمناً لعناوين URL (- بدلاً من +، _ بدلاً من /)، وwithoutPadding() يحذف أحرف = الزائدة.
  • يُدرج getMimeEncoder() فواصل أسطر كل 76 حرفاً — مطلوب لتنسيقي البريد الإلكتروني (MIME) وشهادات PEM.
  • للملفات الكبيرة، استخدم Base64.getEncoder().wrap(OutputStream) للبث دون تحميل الملف بالكامل في الذاكرة.

ما هو ترميز Base64؟

يُحوِّل Base64 أي بيانات ثنائية إلى سلسلة مؤلفة من 64 حرفاً ASCII قابلاً للطباعة: A-Z, a-z, 0-9, +، و /. كل 3 بايتات من المدخلات تُنتج بالضبط 4 أحرف Base64. إذا لم يكن طول المدخلات مضاعفاً لـ 3، يُضاف حرف أو حرفا محاذاة =. الناتج المُرمَّز دائماً أكبر بنحو 33% من البيانات الأصلية.

Base64 ليس تشفيراً. يستطيع أي شخص لديه السلسلة المُرمَّزة فك ترميزها. غرضه هو أمان النقل: رؤوس HTTP وحمولات JSON ومستندات XML ورسائل البريد الإلكتروني هي بروتوكولات نصية لا تستطيع حمل بايتات ثنائية خام دون إفساد. تشمل حالات استخدام Java الشائعة: HTTP Basic Authentication، تضمين شهادات PEM، تخزين البيانات الثنائية في أعمدة نصية بقواعد البيانات، وبناء مقاطع رموز JWT.

After · text
Before · text
ZGVwbG95LXN2Yzpza19saXZlXzRlQzM5SHFMeWpXRGFyanRUMXpkcDdkYw==
deploy-svc:sk_live_4eC39HqLyjWDarjtT1zdp7dc

Base64.getEncoder().encodeToString() — الواجهة البرمجية القياسية

تم إدخال java.util.Base64 في Java 8 كبديل رسمي لـ sun.misc.BASE64Encoder. توفر الفئة ثلاث دوال مصنع ثابتة — كل منها تُعيد نسخة فئة Base64.Encoder المتداخلة — تغطي المتغيرات الثلاثة لـ Base64 المحددة في RFC 4648. لا حاجة لمكتبة خارجية. لا اعتمادية Maven. فقط استيراد واستدعاء.

مثال أدنى — ترميز سلسلة نصية

Java 8+
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class EncodeDemo {
    public static void main(String[] args) {
        String credentials = "monitoring-svc:9f2a7c4e-b1d8-4a3f";
        byte[] credentialBytes = credentials.getBytes(StandardCharsets.UTF_8);

        String encoded = Base64.getEncoder().encodeToString(credentialBytes);
        System.out.println(encoded);
        // bW9uaXRvcmluZy1zdmM6OWYyYTdjNGUtYjFkOC00YTNm
    }
}

الخطوة الأساسية التي يُخطئها معظم مطوري Java في المرة الأولى: يجب تحويل String إلى byte[] قبل الترميز. يعمل Base64 على البايتات وليس على الأحرف. encodeToString() تقبل byte[] وتُعيد String بـ Base64 مباشرةً. إذا احتجت الناتج المُرمَّز كبايتات بدلاً من ذلك، استخدم encode(byte[]) — تُعيد byte[] من أحرف Base64 المُرمَّزة بـ ASCII، وهذا مفيد عند الكتابة مباشرةً إلى OutputStream أو بناء إطارات بروتوكول ثنائي.

HTTP Basic Auth — حالة الاستخدام الأكثر شيوعاً

مصادقة HTTP Basic هي على الأرجح السبب الأكثر شيوعاً لاستخدام مطوري Java لترميز Base64. تتطلب المواصفة (RFC 7617) أن تكون سلسلة بيانات الاعتماد username:password مُرمَّزة بـ Base64 وموضوعة في رأس Authorization. رأيت هذا يُنفَّذ بشكل خاطئ أكثر مما يمكنني إحصاؤه — عادةً بنسيان فاصل النقطتين أو ترميز المكونات بشكل منفصل.

Java — HTTP Basic Auth header
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class BasicAuthExample {
    public static void main(String[] args) throws Exception {
        String username = "metrics-exporter";
        String apiKey = "sk_live_4eC39HqLyjWDarjtT1zdp7dc";

        // username:password → Base64
        String credentials = username + ":" + apiKey;
        String authHeader = "Basic " + Base64.getEncoder()
            .encodeToString(credentials.getBytes(StandardCharsets.UTF_8));

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.example.com/v2/metrics"))
            .header("Authorization", authHeader)
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode());  // 200
    }
}

رحلة ذهاباً وإياباً — الترميز وفك الترميز

Java 8+ — encode and decode round-trip
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class RoundTrip {
    public static void main(String[] args) {
        String original = "X-Correlation-ID: req_8a4f2c91-e7b3-4d56-9012-3f7a8b9c0d1e";

        // الترميز
        String encoded = Base64.getEncoder()
            .encodeToString(original.getBytes(StandardCharsets.UTF_8));
        System.out.println(encoded);
        // WC1Db3JyZWxhdGlvbi1JRDogcmVxXzhhNGYyYzkxLWU3YjMtNGQ1Ni05MDEyLTNmN2E4YjljMGQxZQ==

        // فك الترميز
        byte[] decodedBytes = Base64.getDecoder().decode(encoded);
        String decoded = new String(decodedBytes, StandardCharsets.UTF_8);

        System.out.println(original.equals(decoded));  // true
    }
}
ملاحظة:واجهة برمجة java.util.Base64 متطابقة من Java 8 حتى Java 17 وJava 21. لا حاجة لأي ترحيل عند ترقية JDK. نفس الكود يُترجَم ويعمل على أي إصدار منذ Java 8.

ترميز البيانات غير النصية — byte[] وUUID والطوابع الزمنية

يبدأ ترميز Base64 في Java دائماً بـ byte[]. تُحوَّل السلاسل النصية عبر getBytes(StandardCharsets.UTF_8)، لكن الأنواع الأخرى تحتاج خطوة تحويل أولاً. يجب إجراء تسلسل للـ UUIDs والطوابع الزمنية والمعرِّفات الرقمية إلى تمثيل نصي أو بايتي قبل أن تتمكن من ترميزها بـ Base64.

UUID — الترميز كتمثيل نصي

Java — Base64 encoding a UUID
import java.util.Base64;
import java.util.UUID;
import java.nio.charset.StandardCharsets;

UUID sessionId = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
String encoded = Base64.getEncoder()
    .encodeToString(sessionId.toString().getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
// NmJhN2I4MTAtOWRhZC0xMWQxLTgwYjQtMDBjMDRmZDQzMGM4

UUID مضغوط — ترميز الـ 16 بايت الخام

إذا أردت ناتجاً مُرمَّزاً أقصر، استخرج 128 بت الـ UUID كـ 16 بايت خام بدلاً من تحويله إلى شكله النصي المكوَّن من 36 حرفاً. يتراجع ناتج Base64 من 48 حرفاً إلى 24.

Java — compact UUID encoding
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.UUID;

UUID eventId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");

ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(eventId.getMostSignificantBits());
buffer.putLong(eventId.getLeastSignificantBits());

String compact = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(buffer.array());
System.out.println(compact);
// VQ6EAOKbQdSnFkRmVUQAAA
// 22 حرفاً مقابل 48 للنهج النصي

الطابع الزمني والحمولة المختلطة

Java — encoding a JSON-like payload with timestamp
import java.time.Instant;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// محاكاة حمولة بأسلوب JWT
String payload = String.format(
    "{"sub":"usr_7b3c","iss":"auth.internal","iat":%d,"exp":%d}",
    Instant.now().getEpochSecond(),
    Instant.now().plusSeconds(3600).getEpochSecond()
);

String encoded = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(payload.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
// eyJzdWIiOiJ1c3JfN2IzYyIsImlzcyI6ImF1dGguaW50ZXJuYWwiLCJpYXQiOj... (آمن لـ URL، بدون محاذاة)
تحذير:لا تستدعِ toString() على byte[] متوقعاً الحصول على محتواه — ستحصل على هاش هوية المصفوفة مثل [B@6d06d69c. استخدم new String(bytes, StandardCharsets.UTF_8) أو مرِّر مصفوفة البايت مباشرةً إلى encodeToString().

مرجع دوال Base64.Encoder

تُعرِّض فئة java.util.Base64 ثلاث دوال مصنع، كل منها تُعيد Base64.Encoder مُهيَّئاً لمتغير محدد. نسخ المُرمِّز آمنة للخيوط ولا حالة لها — أنشئها مرة واحدة وأعِد استخدامها.

الدالة
النوع
الوصف
getEncoder()
Base64.Encoder
يُعيد مُرمِّزاً أساسياً وفق RFC 4648 يستخدم الأبجدية القياسية (A-Z, a-z, 0-9, +, /)
getUrlEncoder()
Base64.Encoder
يُعيد مُرمِّزاً يستخدم الأبجدية الآمنة لعناوين URL (- بدلاً من +، _ بدلاً من /)
getMimeEncoder()
Base64.Encoder
يُعيد مُرمِّز MIME الذي يُدرج فواصل أسطر \r\n كل 76 حرفاً
getMimeEncoder(lineLength, lineSeparator)
Base64.Encoder
مُرمِّز MIME بطول سطر وفاصل مخصصَين
encoder.withoutPadding()
Base64.Encoder
يُعيد مُرمِّزاً يحذف أحرف المحاذاة = الزائدة
encoder.encode(byte[])
byte[]
يُرمِّز مصفوفة بايت ويُعيد مصفوفة بايت مُرمَّزة
encoder.encodeToString(byte[])
String
يُرمِّز مصفوفة بايت ويُعيد سلسلة نصية مُرمَّزة مباشرةً
encoder.wrap(OutputStream)
OutputStream
يُغلِّف OutputStream لتشفير Base64 بالبث

Base64.getUrlEncoder() — الترميز الآمن لعناوين URL

يستخدم المُرمِّز الآمن لعناوين URL أبجدية بديلة حيث يصبح + هو - ويصبح / هو _، كما هو محدد في RFC 4648 القسم 5. هذا مهم عندما تظهر سلسلة Base64 في معامل استعلام URL أو اسم ملف أو قيمة cookie — تتعارض أحرف Base64 القياسية مع محددات URL والأحرف المحجوزة لنظام الملفات.

Java — URL-safe Base64 encoding
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String redirectUri = "https://app.internal/callback?state=auth_pending&nonce=9f2a7c";
byte[] data = redirectUri.getBytes(StandardCharsets.UTF_8);

// المُرمِّز القياسي — يحتوي على + و/ تُعطِّل عناوين URL
String standard = Base64.getEncoder().encodeToString(data);
System.out.println(standard);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s/c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw==

// المُرمِّز الآمن لعناوين URL — آمن لمعاملات الاستعلام وأسماء الملفات
String urlSafe = Base64.getUrlEncoder().encodeToString(data);
System.out.println(urlSafe);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s_c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw==

// آمن لعناوين URL بدون محاذاة — لـ JWTs والرموز المدمجة
String noPadding = Base64.getUrlEncoder().withoutPadding().encodeToString(data);
System.out.println(noPadding);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s_c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw

يحذف متغير withoutPadding() أحرف = الزائدة. تتطلب مواصفات JWT ترميز Base64 الآمن لعناوين URL بدون محاذاة لمقاطع الرأس والحمولة، لذا فإن getUrlEncoder().withoutPadding() هو بالضبط الاستدعاء الذي تحتاجه عند إنشاء أو معالجة رموز JWT يدوياً.

ملاحظة:تُعيد دالة withoutPadding() نسخة مُرمِّز جديدة — لا تُعدِّل الأصلية. يمكن تخصيص كليهما في حقول static final وإعادة استخدامها بأمان عبر الخيوط.

الترميز من ملف أو استجابة API

السيناريوان الأكثر شيوعاً في الواقع لترميز Base64 في Java: قراءة ملف ثنائي من القرص (شهادات، صور، حزم إعدادات) وترميز بيانات مستلمة من استجابة HTTP.

ترميز ملف إلى Base64

Java — encoding a file
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public class FileEncoder {
    public static void main(String[] args) {
        try {
            byte[] fileBytes = Files.readAllBytes(Path.of("certs/server.pem"));
            String encoded = Base64.getEncoder().encodeToString(fileBytes);

            System.out.printf("Original: %d bytes%n", fileBytes.length);
            System.out.printf("Encoded:  %d chars%n", encoded.length());

            // كتابة المحتوى المُرمَّز في ملف نصي
            Files.writeString(
                Path.of("certs/server.pem.b64"),
                encoded
            );
        } catch (java.io.IOException e) {
            System.err.println("Failed to read file: " + e.getMessage());
        }
    }
}

ترميز جسم استجابة API

Java 11+ — encoding an HTTP response
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;

public class ApiEncoder {
    public static void main(String[] args) {
        try {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/v2/reports/weekly.pdf"))
                .header("Authorization", "Bearer tok_8f2a9c3d")
                .build();

            HttpResponse<byte[]> response = client.send(
                request, HttpResponse.BodyHandlers.ofByteArray()
            );

            if (response.statusCode() == 200) {
                String encoded = Base64.getEncoder()
                    .encodeToString(response.body());
                System.out.printf("Encoded %d bytes → %d chars%n",
                    response.body().length, encoded.length());
            } else {
                System.err.printf("HTTP %d: %s%n",
                    response.statusCode(),
                    new String(response.body()));
            }
        } catch (Exception e) {
            System.err.println("Request failed: " + e.getMessage());
        }
    }
}

ملاحظة سريعة قبل قسم سطر الأوامر: إذا كنت تحتاج فقط إلى لصق ملف أو استجابة API والحصول على ناتج Base64 دون كتابة كود، مُرمِّز Base64 الإلكتروني يتعامل مع المدخلات النصية والثنائية كليهما.

ترميز Base64 من سطر الأوامر

أحياناً تحتاج فقط إلى ترميز سلسلة نصية أو ملف من الطرفية — بدون مشروع Java، بدون IDE، بدون خطوة بناء. تأتي معظم أنظمة Unix مع أمر base64، وإذا كان لديك JDK مثبتاً، يمكنك استخدام jshell لنهج Java أصلي.

Bash — command-line Base64 encoding
# macOS / Linux — ترميز سلسلة نصية
echo -n "deploy-bot:sk_prod_9f2a7c4e" | base64
# ZGVwbG95LWJvdDpza19wcm9kXzlmMmE3YzRl

# ترميز ملف
base64 < certs/server.pem > certs/server.pem.b64

# باستخدام jshell (JDK 9+)
echo 'System.out.println(java.util.Base64.getEncoder().encodeToString("deploy-bot:sk_prod_9f2a7c4e".getBytes()))' | jshell -

# باستخدام java مباشرةً مع سطر واحد
java -e 'System.out.println(java.util.Base64.getEncoder().encodeToString(args[0].getBytes()))' "my-secret"
# ملاحظة: java -e يتطلب JDK 23+ (JEP 477)

نهج jshell مفيد بشكل خاص عندما تحتاج للتحقق من أن كود Java ينتج نفس الناتج كأداة Unix، أو عند تصحيح عدم تطابق بين ما يرسله خدمتك وما يتوقعه المستقبِل. أحتفظ بـ shell alias لذلك.

ملاحظة:على macOS، يستخدم أمر base64 -D لفك الترميز. على Linux (GNU coreutils) يستخدم -d. سلوك الترميز متطابق على كليهما. علامة -w 0 على Linux تُعطِّل التفاف الأسطر في الناتج، وهو عادةً ما تريده عند التوجيه إلى أوامر أخرى.

Apache Commons Codec — بديل عالي الأداء

لمعظم التطبيقات، java.util.Base64 سريع كفاية. لكن إذا كنت تعالج ملايين عمليات الترميز في حلقة مكثفة — مثل خطوط أنابيب استيعاب السجلات أو وسطاء رسائل عالية الإنتاجية — فإن Apache Commons Codec يستحق الاختبار المعياري. كان موجوداً منذ وقت طويل قبل Java 8 ويوفر بديلاً مجرَّباً مع سطح واجهة برمجية مختلف قليلاً.

Java — Apache Commons Codec
// Maven: org.apache.commons:commons-codec:1.17.0
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.StandardCharsets;

byte[] telemetryPayload = ("{"service":"metrics-collector","
    + ""host":"prod-east-07","
    + ""cpu_pct":72.4,"
    + ""mem_mb":3891,"
    + ""timestamp":1710523200}")
    .getBytes(StandardCharsets.UTF_8);

// الترميز القياسي
String encoded = Base64.encodeBase64String(telemetryPayload);

// الترميز الآمن لعناوين URL
String urlSafe = Base64.encodeBase64URLSafeString(telemetryPayload);

// التحقق من صحة سلسلة Base64
boolean valid = Base64.isBase64(encoded);
System.out.println(valid);  // true

يوفر Apache Commons Codec أيضاً Base64OutputStream و Base64InputStream لسيناريوات البث، ويتضمن دالة تحقق يفتقر إليها مُرمِّز JDK. إذا كان Commons Codec موجوداً بالفعل في شجرة اعتمادياتك (يأتي مع كثير من مشاريع Apache)، فلا يوجد سبب لعدم استخدامه.

Guava BaseEncoding

تتضمن مكتبة Guava من Google BaseEncoding التي توفر واجهة برمجية سلسة لـ Base64 مع فواصل أسطر قابلة للضبط وتحكم في المحاذاة ودعم لكل من الأبجديتين القياسية والآمنة لعناوين URL. الواجهة البرمجية واضحة القراءة، لكن إضافة Guava (نحو 3 ميغابايت) لترميز Base64 فقط مبالغة. إذا كانت Guava موجودة بالفعل في مشروعك لمجموعاتها أو أدوات التخزين المؤقت، فإن واجهة الترميز البرمجية إضافة مرحَّب بها.

Java — Guava BaseEncoding
// Maven: com.google.guava:guava:33.1.0-jre
import com.google.common.io.BaseEncoding;
import java.nio.charset.StandardCharsets;

byte[] webhookPayload = ("{"event":"deployment.completed","
    + ""repo":"payments-api","
    + ""sha":"a7f2c91e4b3d","
    + ""environment":"production"}")
    .getBytes(StandardCharsets.UTF_8);

// Base64 القياسي
String standard = BaseEncoding.base64().encode(webhookPayload);

// آمن لعناوين URL
String urlSafe = BaseEncoding.base64Url().encode(webhookPayload);

// بدون محاذاة
String noPad = BaseEncoding.base64Url().omitPadding().encode(webhookPayload);

// مع فواصل أسطر (بأسلوب PEM)
String wrapped = BaseEncoding.base64()
    .withSeparator("\n", 64)
    .encode(webhookPayload);

Base64.getMimeEncoder() — ناتج MIME وPEM بفواصل أسطر

يُدرج مُرمِّز MIME فواصل أسطر \r\n كل 76 حرفاً، مطابقاً لمواصفة MIME (RFC 2045). تتوقع شهادات PEM ومرفقات بريد S/MIME وبعض الواجهات البرمجية القديمة هذا التنسيق. يُنتج المُرمِّزان القياسي والآمن لعناوين URL سطراً واحداً غير منقطع — إذا مرَّرت ناتجهما إلى نظام يتوقع Base64 بفواصل أسطر، فقد يفشل بصمت أو يرفض البيانات.

Java — MIME Base64 encoding
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// محاكاة جسم شهادة PEM
byte[] certData = new byte[256];  // من الناحية العملية، اقرأ من ملف .der
new java.security.SecureRandom().nextBytes(certData);

// مُرمِّز MIME الافتراضي — 76 حرفاً لكل سطر، فاصل \r\n
String mimeEncoded = Base64.getMimeEncoder().encodeToString(certData);
System.out.println(mimeEncoded);
// QYx2K3p8Xg7JmN1R+wFkLd...  (76 حرفاً)
// Ht5Bv9CzAq0PnSjYl8WxUe...  (76 حرفاً)
// ...

// مُرمِّز MIME مخصص — 64 حرفاً لكل سطر (معيار PEM)، فاصل \n
Base64.Encoder pemEncoder = Base64.getMimeEncoder(64, new byte[]{'\n'});
String pemBody = pemEncoder.encodeToString(certData);
System.out.println("-----BEGIN CERTIFICATE-----");
System.out.println(pemBody);
System.out.println("-----END CERTIFICATE-----");
تحذير:لا تستخدم getMimeEncoder() لرموز JWT أو رؤوس HTTP أو معاملات URL. ستُفسد فواصل الأسطر البيانات في تلك السياقات. استخدم getEncoder() أو getUrlEncoder() بدلاً من ذلك.

بث الملفات الكبيرة باستخدام Base64.getEncoder().wrap()

تحميل ملف كامل في byte[] باستخدام Files.readAllBytes() يعمل للملفات الصغيرة، لكن لأي شيء يتجاوز 50-100 ميغابايت تخاطر بـ OutOfMemoryError. يوفر JDK Base64.getEncoder().wrap(OutputStream)، الذي يُعيد OutputStream يُرمِّز البيانات فورياً أثناء الكتابة إليه. تتدفق البايتات المُرمَّزة عبر التدفق الأساسي دون تخزين المدخلات بأكملها.

Java — streaming Base64 encoding
import java.io.*;
import java.nio.file.*;
import java.util.Base64;

public class StreamingEncoder {
    public static void main(String[] args) throws IOException {
        Path inputPath = Path.of("backups/database-export.sql.gz");
        Path outputPath = Path.of("backups/database-export.sql.gz.b64");

        try (
            InputStream in = Files.newInputStream(inputPath);
            OutputStream fileOut = Files.newOutputStream(outputPath);
            OutputStream base64Out = Base64.getEncoder().wrap(fileOut)
        ) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            long totalBytes = 0;

            while ((bytesRead = in.read(buffer)) != -1) {
                base64Out.write(buffer, 0, bytesRead);
                totalBytes += bytesRead;
            }

            System.out.printf("Streamed %d bytes through Base64 encoder%n", totalBytes);
        }
        // إغلاق base64Out يُفرِّغ بايتات المحاذاة النهائية تلقائياً
    }
}

تتولى كتلة try-with-resources عملية التفريغ والإغلاق. تفصيل واحد يُفاجئ الناس: لا تُكتب محاذاة Base64 النهائية إلا عند إغلاق OutputStream الغلاف. إذا نسيت إغلاقه (أو أغلقت التدفق الخارجي فقط)، فقد تكون الأحرف الأخيرة من ناتجك المُرمَّز مفقودة.

البث إلى مقبس شبكي

تعمل دالة wrap() مع أي OutputStream — ناتج ملف، ناتج مقبس، جسم استجابة HTTP، حتى ByteArrayOutputStream. إليك مثال يكتب بيانات مُرمَّزة بـ Base64 مباشرةً في مخزن مؤقت في الذاكرة، وهو مفيد لاختبارات الوحدة أو بناء حمولات سترسل عبر HTTP:

Java — streaming to ByteArrayOutputStream
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

ByteArrayOutputStream buffer = new ByteArrayOutputStream();

try (OutputStream encoder = Base64.getEncoder().wrap(buffer)) {
    // كتابة البيانات في أجزاء — محاكاة القراءة من تدفق
    encoder.write("chunk-1:telemetry-data-".getBytes(StandardCharsets.UTF_8));
    encoder.write("chunk-2:more-payload-".getBytes(StandardCharsets.UTF_8));
    encoder.write("chunk-3:final-segment".getBytes(StandardCharsets.UTF_8));
}

String encoded = buffer.toString(StandardCharsets.UTF_8);
System.out.println(encoded);
// Y2h1bmstMTp0ZWxlbWV0cnktZGF0YS1jaHVuay0yOm1vcmUtcGF5bG9hZC1jaHVuay0zOmZpbmFsLXNlZ21lbnQ=

// التحقق من رحلة ذهاباً وإياباً
byte[] decoded = Base64.getDecoder().decode(encoded);
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// chunk-1:telemetry-data-chunk-2:more-payload-chunk-3:final-segment
ملاحظة:حجم المخزن المؤقت في مثال البث (8192 بايت) ليس اعتباطياً. يطابق حجم المخزن المؤقت الافتراضي المستخدم بواسطة BufferedInputStream وهو توازن جيد بين استخدام الذاكرة وعبء استدعاءات النظام. المخازن المؤقتة الأصغر تزيد من عدد استدعاءات القراءة/الكتابة؛ المخازن الأكبر تهدر الذاكرة دون تحسين ملموس في الإنتاجية.

نسخ المُرمِّز الآمنة للخيوط — التخزين وإعادة الاستخدام

نسخة Base64.Encoder التي تُعيدها دوال المصنع غير قابلة للتعديل وآمنة للخيوط. استدعاء Base64.getEncoder() في كل عملية ترميز يُنشئ كائناً جديداً في كل مرة. سيُحسِّن JVM هذا على الأرجح، لكن تخزين المُرمِّز في حقل static final يوضح النية ويتجنب تخصيصات غير ضرورية في المسارات الساخنة.

Java — reusable encoder instances
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class TokenService {
    // أنشئ مرة واحدة، أعِد الاستخدام في كل مكان — آمن للخيوط
    private static final Base64.Encoder STANDARD = Base64.getEncoder();
    private static final Base64.Encoder URL_SAFE = Base64.getUrlEncoder().withoutPadding();
    private static final Base64.Encoder MIME = Base64.getMimeEncoder();

    public static String encodeForHeader(String value) {
        return STANDARD.encodeToString(value.getBytes(StandardCharsets.UTF_8));
    }

    public static String encodeForUrl(byte[] data) {
        return URL_SAFE.encodeToString(data);
    }

    public static String encodeForEmail(byte[] attachment) {
        return MIME.encodeToString(attachment);
    }
}

هذا النمط مفيد بشكل خاص في خدمات Spring Boot حيث تتولى فئة مساعدة الترميز عبر مراقبين أو دوال خدمة متعددة. تُعيد دالة withoutPadding() نسخة مُرمِّز جديدة، لذا يمكنك تخزين المتغيرين مع محاذاة وبدون محاذاة كحقلين منفصلين. كل استدعاء لـ encodeToString() أو encode() لا يحمل حالة — لا حاجة لمزامنة، لا حالة مشتركة قابلة للتعديل.

الأخطاء الشائعة

استدعاء getBytes() بدون تحديد مجموعة الأحرف

المشكلة: String.getBytes() بدون وسيطة مجموعة أحرف يستخدم الترميز الافتراضي للمنصة، وهو windows-1252 على Windows وUTF-8 على معظم أنظمة Linux ومتغير على macOS. نفس الكود يُنتج ناتج Base64 مختلفاً على أجهزة مختلفة.

الحل: مرِّر StandardCharsets.UTF_8 دائماً بشكل صريح.

After · Java
Before · Java
String text = "Ключ доступа: prod-east";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(bytes);
String text = "Ключ доступа: prod-east";
byte[] bytes = text.getBytes();  // افتراضي المنصة — غير متوقع
String encoded = Base64.getEncoder().encodeToString(bytes);
استخدام المُرمِّز القياسي لمعاملات URL

المشكلة: يُخرج Base64.getEncoder() أحرف + و/. عند وضعها في سلسلة استعلام URL، يُفسَّر + كمسافة و/ كفاصل مسار، مما يُفسد القيمة بصمت في الطرف المستقبِل.

الحل: استخدم Base64.getUrlEncoder() لأي قيمة ستظهر في URL.

After · Java
Before · Java
// ترميز آمن لعناوين URL — لا أحرف + أو /
String token = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(sessionData);
String url = "https://auth.internal/verify?token=" + token;
// رمز في معامل استعلام URL — سيُعطَّل
String token = Base64.getEncoder()
    .encodeToString(sessionData);
String url = "https://auth.internal/verify?token=" + token;
فك الترميز باستخدام متغير مُرمِّز خاطئ

المشكلة: الترميز باستخدام getUrlEncoder() وفك الترميز باستخدام getDecoder() (أو العكس) يرمي IllegalArgumentException لأن - و_ ليسا صالحَين في أبجدية Base64 القياسية، و+ و/ ليسا صالحَين في الأبجدية الآمنة لعناوين URL.

الحل: افكُّ التشفير دائماً باستخدام المُفكِّك المطابق: getUrlDecoder() للآمن من URL، وgetDecoder() للقياسي.

After · Java
Before · Java
String encoded = Base64.getUrlEncoder()
    .encodeToString(data);
// لاحقاً...
byte[] decoded = Base64.getUrlDecoder()  // مُفكِّك مطابق
    .decode(encoded);
String encoded = Base64.getUrlEncoder()
    .encodeToString(data);
// لاحقاً...
byte[] decoded = Base64.getDecoder()  // مُفكِّك خاطئ
    .decode(encoded);
// IllegalArgumentException إذا احتوى encoded على - أو _
عدم إغلاق wrap() OutputStream

المشكلة: يُخزِّن المُرمِّز المتدفق مؤقتاً ما يصل إلى بايتَين من المدخلات بانتظار مجموعة كاملة من 3 بايتات. إذا لم تُغلق OutputStream الغلاف، لن تُكتب أحرف Base64 الأخيرة (بما في ذلك المحاذاة) أبداً.

الحل: استخدم try-with-resources، أو استدعِ close() صراحةً على التدفق المُغلَّف قبل قراءة الناتج.

After · Java
Before · Java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStream b64os = Base64.getEncoder().wrap(baos)) {
    b64os.write(data);
}  // close() يُفرِّغ المحاذاة النهائية
String encoded = baos.toString();  // مكتمل
ByteArrayOutputStream baos = new ByteArrayOutputStream();
 OutputStream b64os = Base64.getEncoder().wrap(baos);
b64os.write(data);
// baos.toString() غير مكتمل — بايتات نهائية مفقودة

مقارنة دوال ترميز Base64

الدالة
آمن لـ URL
البث
فواصل الأسطر
أنواع مخصصة
يتطلب تثبيتاً
Base64.getEncoder()
✓ (wrap)
لا (JDK 8+)
Base64.getUrlEncoder()
✓ (wrap)
لا (JDK 8+)
Base64.getMimeEncoder()
✓ (wrap)
✓ (76 حرفاً)
لا (JDK 8+)
Apache Commons Codec
اعتمادية Maven
Guava BaseEncoding
✓ (قابل للضبط)
اعتمادية Maven
jcmd / CLI base64
✓ (pipe)
N/A
تثبيت النظام

لمعظم المشاريع: java.util.Base64 هو الاختيار الصحيح. لا اعتمادية، مدمج في JDK، آمن للخيوط، ويغطي جميع متغيرات RFC 4648 الثلاثة. الجأ إلى Apache Commons Codec فقط إذا كان موجوداً بالفعل في classpath وتحتاج دالة التحقق isBase64() أو Base64OutputStream للبث. BaseEncoding من Guava خيار معقول إذا كان مشروعك يعتمد بالفعل على Guava، لكن إضافة اعتمادية بحجم 3 ميغابايت لمجرد Base64 صعب التبرير.

ثلاثة سيناريوات، ثلاثة اختيارات: خدمة ويب قياسية تحتاج ترميز Basic Auth أو JWT؟ التزم بـ JDK. مشروع قديم يسحب Commons Codec عبر Spring أو Apache HTTP Client؟ استخدمه — لا داعي لمكتبتَي Base64 في classpath. مشروع يستخدم Guava للتخزين المؤقت والمجموعات؟ استخدم BaseEncoding لواجهته البرمجية السلسة. لا تضِف مكتبة لمجرد ترميز Base64 — إصدار JDK كافٍ منذ 2014.

إذا احتجت للتحقق سريعاً من ناتج مُرمَّز دون تشغيل كود Java، الصقه في مُرمِّز Base64 للتأكد من تطابق الناتج مع ما يُنتجه كودك.

الأسئلة الشائعة

كيف أُرمِّز سلسلة نصية بـ Base64 في Java؟

حوِّل السلسلة إلى بايتات أولاً باستخدام getBytes(StandardCharsets.UTF_8)، ثم مرِّر مصفوفة البايت إلى Base64.getEncoder().encodeToString(). حدِّد UTF-8 دائماً بشكل صريح — فاستدعاء getBytes() بدون تحديد مجموعة الأحرف يستخدم الافتراضي للمنصة، والذي يتفاوت بين أنظمة التشغيل وإعدادات JVM.

Java
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String payload = "grant_type=client_credentials&scope=read:metrics";
String encoded = Base64.getEncoder()
    .encodeToString(payload.getBytes(StandardCharsets.UTF_8));
// Z3JhbnRfdHlwZT1jbGllbnRfY3JlZGVudGlhbHMmc2NvcGU9cmVhZDptZXRyaWNz

ما الفرق بين Base64.getEncoder() وBase64.getUrlEncoder()؟

كلاهما يُرمِّز بـ Base64، لكن getUrlEncoder() يستخدم الأبجدية الآمنة لعناوين URL المحددة في RFC 4648 القسم 5. يستبدل + بـ - و/ بـ _ حتى يظهر الناتج في عناوين URL وأسماء الملفات دون الحاجة إلى percent-encoding. يستخدم المُرمِّز القياسي + و/ التي تتعارض مع معاملات استعلام URL ومقاطع المسار.

Java
byte[] data = "subject=usr_7b3c&role=admin".getBytes(StandardCharsets.UTF_8);

String standard = Base64.getEncoder().encodeToString(data);
// c3ViamVjdD11c3JfN2IzYyZyb2xlPWFkbWlu

String urlSafe = Base64.getUrlEncoder().encodeToString(data);
// c3ViamVjdD11c3JfN2IzYyZyb2xlPWFkbWlu
// (نفس النتيجة هنا، لكن مع + → - و/ → _ عند ظهور هذه الأحرف)

هل java.util.Base64 هي نفسها في Java 8 وJava 17؟

نعم. لم تتغير واجهة برمجة java.util.Base64 منذ إدخالها في Java 8. الفئة وفئات Encoder وDecoder المتداخلة وجميع دوال المصنع (getEncoder, getUrlEncoder, getMimeEncoder) متطابقة عبر Java 8 و11 و17 و21. لا حاجة لأي ترحيل أو تغييرات في الكود عند ترقية إصدار JDK.

Java
// هذا الكود يُترجَم ويُنفَّذ بشكل متطابق على Java 8 حتى Java 21+
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String encoded = Base64.getEncoder()
    .encodeToString("stable-api".getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);  // c3RhYmxlLWFwaQ==

كيف أُرمِّز ملفاً بـ Base64 في Java؟

اقرأ الملف في مصفوفة بايت باستخدام Files.readAllBytes(Path) ومرِّرها إلى Base64.getEncoder().encodeToString(). للملفات الكبيرة التي لا ينبغي تحميلها بالكامل في الذاكرة، استخدم Base64.getEncoder().wrap(OutputStream) لبث الناتج المُرمَّز.

Java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

byte[] fileBytes = Files.readAllBytes(Path.of("config/tls-cert.pem"));
String encoded = Base64.getEncoder().encodeToString(fileBytes);

لماذا تم إهمال sun.misc.BASE64Encoder؟

كانت sun.misc.BASE64Encoder فئة JDK داخلية لم تكن يوماً جزءاً من الواجهة البرمجية العامة. كانت تقع في حزمة sun.misc التي حذَّرت Oracle صراحةً من استخدامها. أدخلت Java 8 فئة java.util.Base64 كبديل رسمي وعام ومدعوم. منذ Java 9 ونظام الوحدات، يؤدي الوصول إلى فئات sun.misc إلى تحذيرات أو أخطاء حسب إعدادات JDK.

Java
// الطريقة القديمة — لا تستخدمها، تم حذفها في إصدارات JDK الحديثة
// import sun.misc.BASE64Encoder;
// String encoded = new BASE64Encoder().encode(data);

// الطريقة الصحيحة منذ Java 8
import java.util.Base64;
String encoded = Base64.getEncoder().encodeToString(data);

كيف أُرمِّز وأفك ترميز Base64 في Java؟

رمِّز باستخدام Base64.getEncoder().encodeToString(bytes) وافكُّ الترميز باستخدام Base64.getDecoder().decode(encodedString). حوِّل مصفوفة البايت المُفكَّك تشفيرها إلى سلسلة نصية باستخدام new String(bytes, StandardCharsets.UTF_8). تحافظ الرحلة ذهاباً وإياباً على البيانات الأصلية بدقة — طالما تستخدم نفس مجموعة الأحرف لكل من getBytes() و new String().

Java
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// الترميز
String original = "session_token=eyJhbGciOiJSUzI1NiJ9";
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(originalBytes);

// فك الترميز
byte[] decodedBytes = Base64.getDecoder().decode(encoded);
String decoded = new String(decodedBytes, StandardCharsets.UTF_8);

System.out.println(original.equals(decoded));  // true

أدوات ذات صلة

  • Base64 Decoderفك ترميز سلاسل Base64 إلى نصها الأصلي أو شكلها الثنائي — العملية العكسية للترميز.
  • URL Encoderترميز السلاسل النصية بـ percent-encode للاستخدام الآمن في عناوين URL — يختلف عن ترميز Base64 الآمن لعناوين URL، لكنه كثيراً ما يُستخدم جنباً إلى جنب معه.
  • JWT Decoderفحص رموز JWT التي تكون مقاطع رأسها وحمولتها مُرمَّزة بـ Base64url كـ JSON — فك ترميزها بدون مكتبة.
  • JSON Formatterتنسيق حمولات JSON قبل ترميز Base64 أو بعده — مفيد عند تصحيح تكاملات API.
متاح أيضاً بـ:JavaScriptPython
AO
Aisha OseiJava Security & API Engineer

Aisha is a Java engineer specialising in application security, Spring Security, and API design. She has worked on identity and access management systems, OAuth 2.0 integrations, and microservice security at scale. She writes about secure Java coding practices, token validation, cryptographic utilities, and the Spring ecosystem from a security-first perspective.

PN
Pavel Novakالمراجع التقني

Pavel is a backend engineer with deep roots in the JVM ecosystem, working primarily with Java and Kotlin. He has extensive experience building data-intensive services and integrating third-party APIs at scale. He writes about modern Java features, the Jackson ecosystem, serialisation patterns, and practical approaches to keeping large codebases maintainable.