الأدلة التقنية

هل Java مجانية؟ أي JDK تستخدم في شركتك

متى تحتاج اشتراكاً في Oracle JDK ومتى لا تحتاجه، وكيف تعرف نسخ Java التي تعمل عندك وتنقلها إلى نسخة مجانية بأمان.

هل Java مجانية؟ أي JDK تستخدم في شركتك

«هل Java مجانية؟» سؤال يبدو بسيطاً، ولكنه يصل إلى مدير التقنية في الغالب بشكل مخيف: رسالة من مورد تطلب جرداً لكل نسخ Java في الشركة، أو عرض اشتراك يحسب السعر على عدد كل الموظفين، أو مطور يقول إنه نزل Java من موقع Oracle كما يفعل منذ سنوات. والإجابة المختصرة هي أن لغة Java ومشروع OpenJDK مفتوحان ومجانيان، وأن هناك أكثر من نسخة مبنية من هذا الكود تستطيع أن تشغلها في الإنتاج دون أن تدفع شيئاً، وأن الذي تغيرت رخصته أكثر من مرة هو منتج واحد بعينه اسمه Oracle JDK.

وقد شرحنا الفرق بين مواصفة Java والتطبيق المرجعي OpenJDK والتوزيعات المبنية منه في قسم Java في مقال مواصفات لغات البرمجة، ورأينا هناك أن java -version يعطي السطر الأول نفسه في Temurin وCorretto وMicrosoft، وحكينا قصة Java من Sun إلى Oracle في مقال قصة Java من Sun إلى Oracle، فلن نعيد ذلك هنا. هذا المقال عملي: ماذا تقول رخص Oracle بالضبط وبأي تاريخ، ومن يبني النسخ المجانية وكم سنة يدعمها، وكيف تختار، ثم كيف تعرف ما الذي يعمل عندك فعلاً وتنقله إلى نسخة مجانية وتتأكد أنه يتصرف كما كان.

⚠️
هذا المقال ليس استشارة قانونية. نحن ننقل ما تقوله صفحات Oracle والموردين الرسمية بتاريخها كما قرأناها في أكتوبر 2026، والرخص والأسعار تتغير، والنص الملزم هو نص الرخصة أو العقد الذي حصلت به على البرنامج، وصفحة أسئلة Oracle نفسها تقول ذلك. إذا كان عندك عقد أو مراجعة ترخيص License Audit فارجع إلى المختص القانوني في جهتك.

وسوف نناقش في هذا المقال ما يلي:

  • الفرق بين Oracle JDK ونسخ OpenJDK، ولماذا تظهر كلمة Oracle حتى في نسخ مفتوحة.
  • رخص Oracle بتواريخها: رخصة OTN منذ أبريل 2019، ورخصة NFTC منذ سبتمبر 2021 وكم تستمر لكل إصدار، والاشتراك Java SE Universal Subscription الذي يحسب على عدد الموظفين منذ يناير 2023.
  • النسخ المجانية ومن يبنيها: Eclipse Temurin وAmazon Corretto وAzul Zulu وMicrosoft Build of OpenJDK وRed Hat وBellSoft Liberica، برخصها ومدة دعمها من صفحاتها الرسمية.
  • كيف تختار، وكيف تجرد نسخ Java في شركتك.
  • تجربة عملية في Docker: نعرف المورد والإصدار لعملية JVM تعمل، ونفحص مجموعة من ال Docker Images، وننقل خدمة صغيرة من Oracle JDK إلى Temurin ونقارن النتيجة، ونبني Runtime صغيراً بأداتي jdeps وjlink بأحجام حقيقية.

Oracle JDK أم OpenJDK؟

كل نسخة Java تنزلها اليوم مبنية من الكود نفسه في مشروع OpenJDK، والكود برخصة GPLv2 مع استثناء Classpath، أي أنك تستطيع أن تشغله وتوزعه، وتطبيقك الذي يعمل عليه يبقى بالرخصة التي تختارها أنت. ثم تأتي جهات مختلفة فتبني هذا الكود وتختبره وتنشره، والفرق بينها ليس في اللغة، وإنما في ثلاثة أشياء: الرخصة التي تنشر بها الملفات الثنائية Binaries، وكم سنة تستمر في إصدار التحديثات الأمنية لكل إصدار، وهل تبيع دعماً تجارياً لمن يريده. والصورة التالية تبين ذلك:

مستودع OpenJDK في الأعلى برخصة GPLv2 مع استثناء Classpath، وتخرج منه ثماني بطاقات: Oracle JDK برخصة NFTC ثم OTN، وOracle OpenJDK برخصة GPL مع تحديثات لستة أشهر، وEclipse Temurin وAmazon Corretto وMicrosoft Build of OpenJDK وAzul Zulu وRed Hat build of OpenJDK وBellSoft Liberica كلها برخصة GPLv2 مع الاستثناء
كود واحد في OpenJDK، ونسخ كثيرة تختلف في الرخصة ومدة التحديثات

في الصورة أعلاه لاحظ أن Oracle نفسها تنشر نسختين مختلفتين، وهذا مصدر أغلب الخلط:

  • Oracle JDK: منتج Oracle التجاري، تنزله من oracle.com/java، ورخصته اليوم إما NFTC أو OTN بحسب الإصدار وتاريخ التحديث كما سيأتي، وهو الوحيد في الصورة الذي قد تحتاج اشتراكاً لتشغيله في الإنتاج.
  • Oracle OpenJDK: بناء من Oracle على jdk.java.net برخصة GPLv2 مع الاستثناء، مجاني للجميع، ولكن صفحة أسئلة Oracle تبين أن تحديثاته تتوقف بعد ستة أشهر من كل إصدار، فـ Java 25 مثلاً حصل على تحديثات Oracle OpenJDK حتى يناير 2026 فقط، لذلك لا يصلح لمن يريد أن يبقى على إصدار LTS سنوات.

ولاحظ أن الملف الذي يكتب فيه اسم المورد سوف يقول Oracle Corporation في الحالتين، وسوف نرى في التجربة كيف تفرق بينهما.

رخص Oracle JDK بتواريخها

تغيرت رخصة Oracle JDK ثلاث مرات منذ 2019، وكل مرة تنطبق على إصدارات وتحديثات معينة، لذلك اعرف رقم التحديث بالضبط وليس الإصدار فقط، فعبارة «عندنا Java 17 من Oracle» لا تحدد الرخصة. وفيما يلي الرخص الثلاث كما تشرحها صفحات Oracle الرسمية:

قبل 16 أبريل 2019: رخصة BCL

تقول صفحة الأسئلة إن نسخ Oracle Java SE الصادرة قبل 16 أبريل 2019 هي وحدها التي تخضع لرخصة Binary Code License واختصاراً BCL، وإن هذه الرخصة لم تعد تستخدم في الإصدارات الجديدة، وإنك تستطيع أن تستمر في استخدام ما نزلته بالرخصة التي نزلته بها، ولكن هذه النسخ القديمة لا تصلها التحديثات الأمنية ولا ينصح باستخدامها في الإنتاج.

من أبريل 2019: رخصة OTN

في التحديث الربعي الصادر في 16 أبريل 2019 انتقلت تحديثات Java 8 إلى رخصة Oracle Technology Network License Agreement for Java SE، وتاريخ آخر تحديث في نصها 10 أبريل 2019، وجدول الرخص في صفحة الأسئلة يذكر أن Oracle JDK 8u211 وما بعده تحت هذه الرخصة، وأن كل إصدارات Oracle JDK 11 تحتها أيضاً. وهذه الرخصة تسمح بالاستخدام دون مقابل في حالات محددة فقط:

  • الاستخدام الشخصي Personal Use على جهاز مكتبي أو لابتوب، مثل الألعاب أو برامجك الشخصية، والصفحة نفسها تقول إن استخدام Java على جهاز لأي عمل من أعمال الشركة ليس استخداماً شخصياً.
  • التطوير والاختبار والنماذج الأولية Prototyping والعرض Demonstrating لتطبيقاتك.
  • التشغيل مع منتجات Oracle معينة مذكورة في الرخصة (Schedule A وSchedule B)، ومع منتجات معينة في Oracle Cloud Infrastructure.

أما تشغيل تطبيقات شركتك في الإنتاج على Oracle JDK تحت رخصة OTN فلا تسمح به هذه الرخصة، ويحتاج اشتراكاً.

من سبتمبر 2021: رخصة NFTC، ولكن لفترة محددة

مع Java 17 في سبتمبر 2021 بدأت Oracle تستخدم رخصة Oracle No-Fee Terms and Conditions واختصاراً NFTC، وهي تسمح بالاستخدام دون مقابل لكل المستخدمين حتى في الإنتاج وفي العمليات الداخلية للشركة، وتسمح بإعادة التوزيع بشرط ألا تأخذ رسوماً مقابلها، وتشمل بحسب صفحة الأسئلة ال Runtimes التي تبنيها بأداة jlink. ولكن النقطة التي يغفل عنها كثيرون هي أن NFTC لا تبقى مع الإصدار طوال حياته، فبحسب صفحة الأسئلة وخطة الدعم المحدثة في 15 سبتمبر 2026:

  • Java 17: التحديثات حتى سبتمبر 2024، أي حتى 17.0.12، تحت NFTC، والتحديثات من 15 أكتوبر 2024، أي من 17.0.13، تحت OTN.
  • Java 21: تحت NFTC حتى سبتمبر 2026، والتحديثات بدءاً من التحديث الربعي في أكتوبر 2026 مخطط أن تكون تحت OTN. ولاحظ أننا نكتب هذا المقال في أكتوبر 2026، أي أن من يحدث Oracle JDK 21 في الإنتاج يقف الآن على هذه الحافة بالضبط.
  • Java 25: تحت NFTC حتى سبتمبر 2028، أي سنة بعد الإصدار طويل الدعم التالي Java 29 المخطط له في سبتمبر 2027، ثم OTN.
  • الإصدارات غير طويلة الدعم مثل 26 و27: تحت NFTC طوال الأشهر الستة من دعمها.

والقاعدة التي نستخرجها من هذا أن NFTC تعطيك سنة واحدة بعد صدور LTS التالي لتنتقل إليه، فإذا بقيت تحدث Oracle JDK على الإصدار القديم بعد ذلك فقد انتقلت دون أن تشعر إلى رخصة لا تسمح بالإنتاج. والصورة التالية تجمع الرخص الثلاث لكل إصدار طويل الدعم:

خمسة أشرطة زمنية من 2014 إلى 2030: Java 8 رخصة BCL حتى أبريل 2019 ثم OTN، وJava 11 كلها OTN، وJava 17 رخصة NFTC حتى 17.0.12 في سبتمبر 2024 ثم OTN، وJava 21 رخصة NFTC حتى سبتمبر 2026 ثم OTN من تحديث أكتوبر 2026، وJava 25 رخصة NFTC حتى سبتمبر 2028 ثم OTN، وخط أحمر متقطع عند أكتوبر 2026
رخصة Oracle JDK لكل إصدار طويل الدعم بحسب تاريخ التحديث

الاشتراك Java SE Universal Subscription: السعر على عدد الموظفين

إذا احتجت Oracle JDK في الإنتاج تحت OTN فالطريق الرسمي هو الاشتراك، وتقول صفحة أسئلة الاشتراك إن Java SE Universal Subscription حل محل الاشتراكات القديمة Java SE Subscription وJava SE Desktop Subscription منذ 23 يناير 2023، وإنه يباع بمقياس الموظف Employee، وإن طريقة النشر والبنية لا تؤثر في السعر. والمهم هنا هو تعريف الموظف، فبحسب قائمة الأسعار المؤرخة في 8 أكتوبر 2026 فهو كل موظفيك بدوام كامل أو جزئي أو مؤقت، مع كل موظفي الوكلاء والمقاولين والمتعاقدين الخارجيين والمستشارين الذين يدعمون عملياتك الداخلية، والقائمة تقول بوضوح إن العدد يحسب «بغض النظر عن عدد الموظفين الذين يستخدمون البرامج».

والأسعار المنشورة اليوم كما يلي:

عدد الموظفينالسعر المنشور
حتى 10100 دولار في السنة
من 11 إلى 1001,000 دولار في السنة
من 101 إلى 50010,000 دولار في السنة
من 501 إلى 2,99910,000 دولار في السنة، و147.20 دولاراً في السنة لكل موظف فوق 500
من 3,000 إلى 9,99910.50 دولار للموظف في الشهر
من 40,000 إلى 49,9995.25 دولار للموظف في الشهر، وأكثر من 50,000 بالتواصل

ولنأخذ مثالين من قائمة الأسعار نفسها: شركة فيها 800 موظف تدفع 54,160 دولاراً في السنة، وجهة فيها 3,000 موظف تدفع 378,000 دولار في السنة، حتى لو كان الذي يستخدم Java فيها فريقاً من عشرة مطورين وثلاثة سيرفرات. لذلك فإن السؤال الأول ليس «كم سيرفراً عندنا عليه Java؟» وإنما «هل نحتاج Oracle JDK أصلاً؟»، وفي أغلب الحالات التي نراها فالإجابة لا، والبديل كما سيأتي مبني من الكود نفسه.

النسخ المجانية ومن يبنيها

الجدول التالي يجمع النسخ المجانية الأشهر مع الرخصة ومدة التحديثات للإصدارات طويلة الدعم، والتواريخ كلها من الصفحة الرسمية لكل مورد كما قرأناها في أكتوبر 2026، ولاحظ أن كل مورد يكتب تاريخاً بمعنى مختلف قليلاً، فاقرأ عمود «معنى التاريخ» قبل أن تقارن:

النسخةمن يبنيهاالرخصةمعنى التاريخ811172125
Eclipse Temurinمشروع Adoptium في مؤسسة EclipseGPLv2+CEآخر إتاحة، «على الأقل»12/203010/202710/202712/20299/2031
Amazon CorrettoAmazonGPLv2+CEنهاية الدعم المجاني12/20301/203210/202910/203010/2032
Microsoft Build of OpenJDKMicrosoftGPLv2+CEأقرب نهاية للدعملا يوجد9/20279/20279/20289/2030
Azul ZuluAzulGPLv2+CEنهاية الدعم التجاري Azul Core12/20301/20329/20299/20319/2033
Red Hat build of OpenJDKRed HatGPLv2+CEنهاية الدعم الكامل مع RHEL11/2026انتهى 10/202412/202712/202912/2030
BellSoft LibericaBellSoftGPLv2+CEنهاية الدعم التجاري3/20313/20323/20303/20323/2034

في الجدول أعلاه لاحظ التالي:

  • Temurin: تقول أسئلة Adoptium إن Temurin مجاني للاستخدام «إلى الأبد» برخصة GPLv2 مع استثناء Classpath، وتقول صفحة الدعم إن الإصدارات طويلة الدعم تدعم أربع سنوات على الأقل، وإن المشروع يستمر في بنائها ما دام الكود المصدري لها يحدث في OpenJDK. والدعم هنا هو دعم المجتمع عبر البلاغات، أما الدعم التجاري بعقد فتقدمه شركات أخرى تذكرها صفحتهم.
  • Corretto: تقول Amazon إنه موزع برخصة مفتوحة دون أي مقابل، وإن الدعم الطويل يشمل التحديثات الأمنية وتحسينات الأداء دون مقابل حتى التاريخ المذكور، وإنه يجتاز اختبارات التوافق TCK.
  • Microsoft: التحديثات الربعية مجانية للجميع وتستطيع نشرها في أي مكان بحسب الرخصة، أما الدعم التجاري فهو فقط لعملاء Azure على أحمال Azure، ولا توجد عندهم نسخة Java 8.
  • Azul Zulu: تقول وثائق Azul إن Zulu نسخ مجانية الاستخدام، والتواريخ في الجدول هي تواريخ الدعم التجاري في اشتراك Azul Core، ولم نجد في صفحات Azul العامة تاريخاً مكتوباً لتوقف التحديثات المجانية لكل إصدار، لذلك إذا اخترت Zulu دون اشتراك فتابع صفحة التنزيل بنفسك.
  • Red Hat: الدعم مشمول في اشتراك RHEL، والدعم الممتد ELS لـ Java 11 حتى يناير 2032 مدفوع باشتراك منفصل، وفي التجربة نزلنا Docker Image الخاصة بـ OpenJDK 21 من سجل Red Hat العام دون تسجيل دخول.
  • Liberica: تقول صفحة Liberica إنها مجانية تماماً في الإنتاج، والتواريخ في الجدول للدعم التجاري الذي تقول BellSoft إنه ثماني سنوات على الأقل للإصدارات طويلة الدعم.

كيف تختار؟

قد يتساءل البعض: إذا كانت كلها من الكود نفسه، فهل يهم أيها نختار؟ والإجابة نعم، ولكن ليس للسبب الذي يتوقعه أغلب الناس، فأداء البرنامج وسلوكه سيكون واحداً في الغالب كما سنرى في التجربة، والذي يختلف هو من سيصدر لك التحديث الأمني القادم ومتى، ومن ستتصل به إذا تعطل شيء. لذلك نقترح الترتيب التالي:

  1. إذا كنت على سحابة أو نظام تشغيل له نسخة خاصة، فابدأ بها: على AWS فـ Corretto، وعلى Azure فـ Microsoft Build of OpenJDK لأن الدعم التجاري مشمول، وعلى RHEL أو OpenShift فـ Red Hat build of OpenJDK لأنها مشمولة في اشتراكك.
  2. إذا لم يكن عندك ما سبق: Temurin هو الخيار المحايد الذي لا تملكه شركة واحدة، ولكل إصدار منه Docker Image رسمية، وهو ما نستخدمه في تجارب الموقع.
  3. إذا كنت تحتاج عقد دعم بمستوى خدمة SLA: فاشترك مع Azul أو BellSoft أو Red Hat أو أحد الموردين الذين تذكرهم Adoptium، أو مع Oracle، وقارن السعر بطريقة الحساب لا بالرقم فقط، فبعضها على عدد الأجهزة وبعضها على عدد الموظفين.
  4. إذا كان تطبيقك من مورد خارجي: فاسأله عن النسخة التي يدعمها رسمياً، فبعض المنتجات تشترط Oracle JDK في عقد الدعم، وصفحة أسئلة Oracle تذكر أن بعض الموردين عندهم اتفاقية مع Oracle تغطي Java التي تأتي مع منتجهم.
  5. وفي كل الحالات: اختر إصدار LTS، وثبت الرقم الكامل في Dockerfile، وخطط للانتقال إلى LTS التالي قبل نهاية الدعم بسنة.

كيف تعرف ما الذي يعمل في شركتك؟

قبل أن تختار شيئاً، اعرف ما عندك، لأن الخطر الحقيقي ليس النسخة التي اخترتها عن وعي، وإنما Oracle JDK التي ثبتها شخص ما على سيرفر قبل خمس سنوات، أو التي تأتي داخل Docker Image لا يعرف أحد ما فيها. وأماكن البحث أربعة:

  • العمليات التي تعمل الآن: كل JVM تعمل تستطيع أن تسألها عن نفسها، إما بأمر java -XshowSettings:properties -version من مسار Java الذي تستخدمه، أو بأمر jcmd مع رقم العملية إذا كانت أدوات JDK موجودة.
  • الملفات على السيرفرات: كل نسخة Java حديثة فيها ملف نصي اسمه release في مجلدها الرئيسي، فيه اسم المورد والإصدار، والبحث عنه أسرع من تشغيل كل نسخة.
  • ال Docker Images: هنا يختفي أغلب Java الذي لا تعرفه، لأن كثيراً من المنتجات مثل محركات البحث وأدوات الرسائل تحمل JDK خاصاً بها داخل ال Image.
  • أجهزة الموظفين: وهذه لا تغطيها التجربة، ولكن تذكر أن مقياس الموظف في اشتراك Oracle يجعل الجهاز المكتبي الذي عليه Oracle JDK بنفس أهمية السيرفر، وأدوات إدارة الأجهزة في جهتك هي الطريق لجردها.

تجربة عملية

في هذه التجربة سوف نشغل خدمة Java صغيرة على Oracle JDK 25 ثم على Temurin 25، ونعرف المورد في كل منهما بثلاث طرق، ثم نفحص مجموعة من ال Docker Images بسكربت لا يشغلها، ثم ننقل الخدمة ونقارن النتيجة، ونكسر سكربت تشغيل قديماً، ونبني في النهاية Runtime صغيراً بأداة jlink. والتجربة على Docker 29.8.0، وكل ال Containers تبدأ بالاسم lab-java- والمنافذ مربوطة بالعنوان 127.0.0.1 فقط.

الخدمة التي سوف ننقلها

الخدمة ملف واحد App.java يحسب أشياء نريدها أن تبقى كما هي بعد النقل: ترتيب قائمة فواتير، ومجموع مبلغ بتنسيق الريال، وهاش SHA-256 لنص، ووقت بتوقيت الرياض، والترميز الافتراضي، وبروتوكولات TLS المتاحة، وتعرضها على المنفذ 8080 بسيرفر HTTP الموجود في JDK:

import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.NumberFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;

// A small service used in the lab: "check" prints results that must not depend on the JDK vendor,
// "serve" exposes the same results over HTTP on port 8080.
public class App {
    record Invoice(String id, long halalas) {}

    static String report() throws Exception {
        var invoices = List.of(new Invoice("INV-3", 125_050), new Invoice("INV-1", 9_900), new Invoice("INV-2", 47_500));
        long total = invoices.stream().mapToLong(Invoice::halalas).sum();
        var sorted = invoices.stream().map(Invoice::id).sorted().toList();
        var sha = MessageDigest.getInstance("SHA-256").digest("arabroot".getBytes(StandardCharsets.UTF_8));
        var riyadh = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z").withZone(ZoneId.of("Asia/Riyadh"));
        var money = NumberFormat.getCurrencyInstance(Locale.forLanguageTag("en-SA"));
        return String.join("\n",
            "sorted   = " + sorted,
            "total    = " + money.format(total / 100.0),
            "sha256   = " + HexFormat.of().formatHex(sha),
            "riyadh   = " + riyadh.format(Instant.parse("2026-10-01T09:00:00Z")),
            "charset  = " + java.nio.charset.Charset.defaultCharset(),
            "tls      = " + String.join(",", javax.net.ssl.SSLContext.getDefault().getSupportedSSLParameters().getProtocols()));
    }

    public static void main(String[] args) throws Exception {
        if (args.length > 0 && args[0].equals("serve")) {
            var server = HttpServer.create(new InetSocketAddress(8080), 0);
            server.createContext("/", ex -> {
                byte[] body;
                try { body = (report() + "\n").getBytes(StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException(e); }
                ex.sendResponseHeaders(200, body.length);
                try (var out = ex.getResponseBody()) { out.write(body); }
            });
            server.start();
            System.out.println("listening on 8080, vendor " + System.getProperty("java.vendor") + " " + System.getProperty("java.runtime.version"));
        } else {
            System.out.println(report());
        }
    }
}

ثم ملفا Dockerfile لا يختلفان إلا في سطر FROM، الأول على Docker Image الرسمية من Oracle لـ Oracle JDK 25 تحت رخصة NFTC، والثاني على Temurin بالإصدار نفسه:

# Dockerfile.oracle
# The service as it often runs today: on the Oracle JDK image (NFTC licence for JDK 25 updates until September 2028)
FROM container-registry.oracle.com/java/jdk-no-fee-term:25
WORKDIR /app
COPY app/App.java .
RUN javac -d classes App.java
CMD ["java", "-cp", "classes", "App", "serve"]
# Dockerfile.temurin
# The same service after the move: only the FROM line changes
FROM eclipse-temurin:25.0.4.1_1-jdk
WORKDIR /app
COPY app/App.java .
RUN javac -d classes App.java
CMD ["java", "-cp", "classes", "App", "serve"]

ولاحظ أن Oracle تنشر Oracle JDK في سجلها container-registry.oracle.com، وقد حاولنا أولاً تنزيل container-registry.oracle.com/java/jdk:21 فرفض السجل الطلب بالخطأ 401 Unauthorized لأنه يحتاج حساباً، أما المستودع java/jdk-no-fee-term الذي ينشر الإصدارات تحت NFTC فنزل دون تسجيل دخول. الآن نبني ونشغل الخدمتين:

docker build -f Dockerfile.oracle -t lab-java-app:oracle .
docker build -f Dockerfile.temurin -t lab-java-app:temurin .
docker run -d --name lab-java-oracle -p 127.0.0.1:18081:8080 lab-java-app:oracle
docker run -d --name lab-java-temurin -p 127.0.0.1:18082:8080 lab-java-app:temurin
docker logs lab-java-oracle; docker logs lab-java-temurin

والمخرج سوف يكون كما يلي:

listening on 8080, vendor Oracle Corporation 25.0.4.1+1-LTS-5
listening on 8080, vendor Eclipse Adoptium 25.0.4.1+1-LTS

من أي مورد هذه ال JVM؟ ثلاث طرق

الطريقة الأولى هي أن تسأل Java نفسها عن خصائصها System Properties، والخيار -XshowSettings:properties يطبعها كلها، ونحن نأخذ منها الأسطر التي تهمنا:

docker exec lab-java-oracle java -XshowSettings:properties -version 2>&1 \
  | grep -E '^ *java\.(vendor|vm\.vendor|vendor\.version|runtime\.version|runtime\.name|home|vm\.name) '
docker exec lab-java-temurin java -XshowSettings:properties -version 2>&1 \
  | grep -E '^ *java\.(vendor|vm\.vendor|vendor\.version|runtime\.version|runtime\.name|home|vm\.name) '

والمخرج سوف يكون كما يلي، الأول من Oracle JDK والثاني من Temurin:

    java.home = /usr/java/jdk-25
    java.runtime.name = Java(TM) SE Runtime Environment
    java.runtime.version = 25.0.4.1+1-LTS-5
    java.vendor = Oracle Corporation
    java.vm.name = Java HotSpot(TM) 64-Bit Server VM
    java.vm.vendor = Oracle Corporation

    java.home = /opt/java/openjdk
    java.runtime.name = OpenJDK Runtime Environment
    java.runtime.version = 25.0.4.1+1-LTS
    java.vendor = Eclipse Adoptium
    java.vendor.version = Temurin-25.0.4.1+1
    java.vm.name = OpenJDK 64-Bit Server VM
    java.vm.vendor = Eclipse Adoptium

في المخرج أعلاه لاحظ التالي:

  • العلامة الأوضح على Oracle JDK التجاري هي java.runtime.name = Java(TM) SE Runtime Environment واسم الآلة Java HotSpot(TM)، أما كل نسخ OpenJDK فتكتب OpenJDK Runtime Environment، وهذا يشمل نسخة Oracle OpenJDK المفتوحة.
  • الخاصية java.vendor.version موجودة في Temurin وتحمل اسم البناء، وغير موجودة أصلاً في Oracle JDK.
  • رقم الإصدار في Oracle JDK ينتهي بلاحقة رقم البناء الداخلي -5، وهذا ليس فرقاً في الكود.

الطريقة الثانية لا تحتاج تشغيل Java أصلاً، وهي قراءة الملف release في مجلد Java:

docker exec lab-java-oracle cat /usr/java/jdk-25/release
docker exec lab-java-temurin cat /opt/java/openjdk/release

والمخرج سوف يكون كما يلي، وقد حذفنا السطر الطويل MODULES من الاثنين:

IMPLEMENTOR="Oracle Corporation"
JAVA_RUNTIME_VERSION="25.0.4.1+1-LTS-5"
JAVA_VERSION="25.0.4.1"
JAVA_VERSION_DATE="2026-08-18"
LIBC="gnu"
OS_ARCH="x86_64"
OS_NAME="Linux"
SOURCE=".:git:d5c355d21fdd open:git:2e8d1990fcae"

IMPLEMENTOR="Eclipse Adoptium"
IMPLEMENTOR_VERSION="Temurin-25.0.4.1+1"
JAVA_RUNTIME_VERSION="25.0.4.1+1-LTS"
JAVA_VERSION="25.0.4.1"
JAVA_VERSION_DATE="2026-08-18"
LIBC="gnu"
OS_ARCH="x86_64"
OS_NAME="Linux"
SOURCE=".:git:520406d87195"
BUILD_SOURCE="git:e6ba7dec3d07654074559310376a3ae89da5f4ac"
BUILD_SOURCE_REPO="https://github.com/adoptium/temurin-build.git"
SOURCE_REPO="https://github.com/adoptium/jdk25u.git"
FULL_VERSION="25.0.4.1+1-LTS"
SEMANTIC_VERSION="25.0.4.1+1"
BUILD_INFO="OS: Linux Version: 6.8.0-1064-azure"
JVM_VARIANT="Hotspot"
JVM_VERSION="25.0.4.1+1-LTS"
IMAGE_TYPE="JDK"

ولاحظ السطر SOURCE في Oracle JDK، ففيه مستودعان: الأول هو كود Oracle الداخلي والثاني بعد الكلمة open هو كود OpenJDK، بينما يشير ملف Temurin إلى مستودع عام واحد تستطيع أن تفتحه بنفسك، وهذه علامة ثانية تفرق بها بين Oracle JDK ونسخة مفتوحة تحمل اسم Oracle.

الطريقة الثالثة هي سؤال عملية تعمل فعلاً، وهذا مفيد عندما يكون على السيرفر أكثر من نسخة Java ولا تعرف أيها شغلت التطبيق، والأداة jcmd تعرض العمليات ثم تسأل كل واحدة عن نسختها:

docker exec lab-java-oracle jcmd -l
docker exec lab-java-oracle jcmd 1 VM.version
docker exec lab-java-temurin jcmd 1 VM.version

والمخرج سوف يكون كما يلي:

1 App serve
90 jdk.jcmd/sun.tools.jcmd.JCmd -l
1:
Java HotSpot(TM) 64-Bit Server VM version 25.0.4.1+1-LTS-5
JDK 25.0.4.1
1:
OpenJDK 64-Bit Server VM version 25.0.4.1+1-LTS
JDK 25.0.4.1

ولاحظ أن jcmd من أدوات JDK، فإذا كانت ال Image تحتوي على JRE فقط، كما في Image الخاصة بـ Red Hat التي سنراها بعد قليل، فلن تجده، وعندها يبقى لك ملف release.

فحص ال Docker Images دون تشغيلها

الآن نريد أن نعرف ما في ال Images، والطريقة البسيطة هي أن نشغل كل Image ونطلب منها java -version، ولكن هذه الطريقة لا تصلح، والسبب أن كثيراً من ال Images تبدأ تطبيقها عند التشغيل أو لا تحتوي على Shell، وأن Java قد لا تكون في المسار PATH أصلاً. لذلك كتبنا سكربت ينشئ Container متوقفاً من كل Image دون أن يشغله، ويبحث عن مجلد Java من متغير JAVA_HOME في إعدادات ال Image، فإذا لم يجده يبحث في نظام الملفات عن الملف lib/modules الذي يوجد في كل Runtime حديثة، ثم ينسخ ملف release إلى الخارج ويقرؤه:

#!/usr/bin/env bash
# Reads the JDK vendor and version inside Docker Images without running them:
# creates a stopped container, finds the Java home and copies its "release" file out.
#   ./jdk-scan.sh image1 image2 ...      (or: docker images --format '{{.Repository}}:{{.Tag}}' | xargs ./jdk-scan.sh)
export MSYS_NO_PATHCONV=1
printf '%-58s %-22s %-24s %s\n' IMAGE IMPLEMENTOR JAVA_RUNTIME_VERSION JAVA_HOME
for img in "$@"; do
  c=lab-java-scan
  docker rm -f "$c" >/dev/null 2>&1
  docker create --name "$c" "$img" >/dev/null 2>&1 || { printf '%-58s %s\n' "$img" "cannot create container"; continue; }
  # 1) JAVA_HOME from the image environment
  jh=$(docker image inspect "$img" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^JAVA_HOME=//p' | head -1)
  # 2) otherwise look for the runtime's lib/modules file in the image filesystem
  if [ -z "$jh" ]; then
    # read the whole listing (sed reads to the end, so docker export is never cut off by a closed pipe)
    m=$(docker export "$c" | tar -t 2>/dev/null | grep -E '(^|/)lib/modules$' | sed -n 1p)
    [ -n "$m" ] && jh="/${m%/lib/modules}"
  fi
  rel=$( [ -n "$jh" ] && docker cp -L "$c:$jh/release" - 2>/dev/null | tar -xO 2>/dev/null )
  get() { printf '%s\n' "$rel" | sed -n "s/^$1=\"\(.*\)\"/\1/p"; }
  if [ -n "$rel" ]; then
    printf '%-58s %-22s %-24s %s\n' "$img" "$(get IMPLEMENTOR)" "$(get JAVA_RUNTIME_VERSION)" "$jh"
  else
    printf '%-58s %-22s %-24s %s\n' "$img" "-" "-" "${jh:-no JDK found}"
  fi
  docker rm "$c" >/dev/null
done

ثم نشغله على Images من الموردين الستة بالإصدار 21، وعلى Image من Oracle، وعلى Image تطبيقنا على Oracle JDK وImage الخاصة بـ jlink التي سنبنيها في آخر التجربة، وعلى أربع Images لمنتجات معروفة مبنية بـ Java كانت موجودة على جهاز التجربة:

./jdk-scan.sh eclipse-temurin:21-jdk amazoncorretto:21 mcr.microsoft.com/openjdk/jdk:21-ubuntu \
  azul/zulu-openjdk:21 bellsoft/liberica-openjdk-debian:21 registry.access.redhat.com/ubi9/openjdk-21-runtime:latest \
  container-registry.oracle.com/java/jdk-no-fee-term:25 lab-java-app:oracle lab-java-app:jlink \
  docker.elastic.co/elasticsearch/elasticsearch:9.5.5 docker.elastic.co/logstash/logstash:9.5.5 \
  opensearchproject/opensearch:3.9.0 apache/kafka:4.3.1

والمخرج سوف يكون كما يلي:

IMAGE                                                      IMPLEMENTOR            JAVA_RUNTIME_VERSION     JAVA_HOME
eclipse-temurin:21-jdk                                     Eclipse Adoptium       21.0.12.1+1-LTS          /opt/java/openjdk
amazoncorretto:21                                          Amazon.com Inc.        21.0.12.1+12-LTS         /usr/lib/jvm/java-21-amazon-corretto
mcr.microsoft.com/openjdk/jdk:21-ubuntu                    Microsoft              21.0.12.1+1-LTS          /usr/lib/jvm/msopenjdk-21
azul/zulu-openjdk:21                                       Azul Systems, Inc.     21.0.12.1+1-LTS          /usr/lib/jvm/zulu21
bellsoft/liberica-openjdk-debian:21                        BellSoft               21.0.12.1+1-LTS          /usr/lib/jvm/jdk-21.0.12.1-bellsoft-x86_64
registry.access.redhat.com/ubi9/openjdk-21-runtime:latest  Red Hat, Inc.          21.0.12.1+1-LTS          /usr/lib/jvm/jre
container-registry.oracle.com/java/jdk-no-fee-term:25      Oracle Corporation     25.0.4.1+1-LTS-5         /usr/java/jdk-25
lab-java-app:oracle                                        Oracle Corporation     25.0.4.1+1-LTS-5         /usr/java/jdk-25
lab-java-app:jlink                                                                                         /opt/java
docker.elastic.co/elasticsearch/elasticsearch:9.5.5        Oracle Corporation     27+35-2325               /usr/share/elasticsearch/jdk
docker.elastic.co/logstash/logstash:9.5.5                  Eclipse Adoptium       21.0.12.1+1-LTS          /usr/share/logstash/jdk
opensearchproject/opensearch:3.9.0                         Eclipse Adoptium       25.0.4.1+1-LTS           /usr/share/opensearch/jdk
apache/kafka:4.3.1                                         Eclipse Adoptium       21.0.11+10-LTS           /opt/java/openjdk

في المخرج أعلاه لاحظ التالي:

  • كل مورد يكتب اسمه في IMPLEMENTOR، ولكل واحد مسار مختلف لمجلد Java، وهذا وحده سبب كاف لأن تستخدم $JAVA_HOME في سكربتاتك بدل المسارات الثابتة كما سنرى بعد قليل.
  • Image الخاصة بـ Red Hat فيها JRE فقط في /usr/lib/jvm/jre، أي بدون أدوات JDK مثل jcmd، وملف release هو طريقك الوحيد فيها.
  • Elasticsearch تحمل Java 27 والمورد المكتوب Oracle Corporation، فهل هذا Oracle JDK التجاري؟ لا، والدليل أننا شغلنا Java الموجودة داخلها بالخيار -XshowSettings:properties فكانت java.runtime.name = OpenJDK Runtime Environment ورقم الإصدار 27+35-2325، أي بناء Oracle OpenJDK المفتوح برخصة GPL، ولهذا لا تحكم على الرخصة من كلمة Oracle وحدها.
  • Logstash وOpenSearch وKafka تحمل Temurin، ولكن بإصدارات مختلفة: Logstash على 21، وOpenSearch على 25، وKafka على 21.0.11 بينما آخر تحديث لـ Temurin 21 في القائمة نفسها 21.0.12.1، أي أن Java داخل المنتج متأخرة بتحديث أمني، ولن تتحدث إلا عندما يصدر المنتج نفسه إصداراً جديداً، وهذا سبب إضافي لأن يكون الجرد دورياً.
  • Image الخاصة بـ jlink ظهرت بلا مورد ولا إصدار، لأن jlink يكتب في ملف release سطرين فقط هما JAVA_VERSION="25.0.4.1" وMODULES="java.base jdk.httpserver"، لذلك إذا بنيت Runtime مخصصاً فاكتب المورد والإصدار في Labels الخاصة بال Image حتى يجدها من يجرد بعدك.
  • Elasticsearch وLogstash لا تعرفان المتغير JAVA_HOME، فوصل إليهما السكربت بالطريقة الثانية، أي بقراءة نظام الملفات كاملاً عبر docker export، وهذه الطريقة بطيئة جداً مع ال Images الكبيرة، فقد أخذت عشرات الدقائق لكل واحدة منهما على جهاز التجربة، لذلك شغل الجرد في وقت هادئ.

والأمر الذي استخدمناه لفحص Java داخل Elasticsearch يشغل Java الموجودة في ال Image مباشرة بدل التطبيق، عبر الخيار --entrypoint:

docker run --rm --name lab-java-es --entrypoint /usr/share/elasticsearch/jdk/bin/java docker.elastic.co/elasticsearch/elasticsearch:9.5.5 -XshowSettings:properties -version 2>&1 | grep -E '^ *java\.(vendor|runtime\.name|runtime\.version|vm\.name) '

والمخرج سوف يكون كما يلي:

    java.runtime.name = OpenJDK Runtime Environment
    java.runtime.version = 27+35-2325
    java.vendor = Oracle Corporation
    java.vm.name = OpenJDK 64-Bit Server VM

وفي شركة حقيقية تستطيع أن تمرر للسكربت كل ال Images التي تعمل في سيرفراتك، أو كل ال Images في سجلك الداخلي، وتحفظ النتيجة كجرد Inventory تعيده كل ربع سنة، وقد شرحنا في مقال أدوات البناء لماذا تثبت رقم الإصدار الكامل في ملف البناء حتى لا يتغير ما تحت تطبيقك دون علمك.

النقل من Oracle JDK إلى Temurin: هل تغير شيء؟

الآن نطلب من الخدمتين النتيجة نفسها ونقارن المخرجين سطراً بسطر:

curl -s http://127.0.0.1:18081/ > oracle.out
curl -s http://127.0.0.1:18082/ > temurin.out
cat oracle.out
diff oracle.out temurin.out && echo "no difference"

والمخرج سوف يكون كما يلي:

sorted   = [INV-1, INV-2, INV-3]
total    = SAR1,824.50
sha256   = 8f7e10d9a19b4b1d8ffb8edb7e759a154a5007f9aa27f7171339d2e01b83ad7e
riyadh   = 2026-10-01 12:00 GMT+03:00
charset  = UTF-8
tls      = TLSv1.3,TLSv1.2,TLSv1.1,TLSv1,SSLv3,SSLv2Hello
no difference

أي أن الترتيب وتنسيق العملة والهاش والمنطقة الزمنية والترميز والبروتوكولات المتاحة واحدة في الاثنين، والسبب أن هذه كلها من مكتبات OpenJDK نفسها وقاعدة بيانات المناطق الزمنية وبيانات اللغات CLDR التي تأتي معها. والفرق الوحيد الذي رأيناه كان في الحجم، فبحسب docker images كانت Image الخدمة على Oracle JDK بحجم 1.12GB، وعلى Temurin بحجم 597MB، وعندما قسنا داخل ال Containers بالأمر du -sh وجدنا أن مجلد Oracle JDK نفسه 376MB على نظام Oracle Linux 9.8، ومجلد Temurin 252MB على Ubuntu 26.04، أي أن الفرق من الاثنين معاً: نظام التشغيل الأساسي ومحتوى مجلد JDK.

وهذه نتيجة خدمة صغيرة، ولا تكفي وحدها لتطبيق كبير، لذلك عند النقل الحقيقي شغل اختباراتك الآلية كاملة على النسخة الجديدة، وراقب الخدمة فترة على بيئة الاختبار، وانتبه لثلاثة أشياء خارج الكود سوف نراها في الخطوة التالية.

اكسرها: سكربت التشغيل القديم

في أغلب الشركات لا يكسر النقل الكود، وإنما يكسر ما حوله، ولنأخذ سكربت تشغيل من النوع الذي نراه كثيراً، يتحقق من أن Java «صحيحة» بقراءة السطر الأول من java -version، ويستخدم مساراً ثابتاً:

#!/bin/sh
# An old start script of the kind found in many companies: it trusts the "java version" line and a fixed path
JAVA=/usr/java/jdk-25/bin/java
if ! java -version 2>&1 | head -1 | grep -q '^java version'; then
  echo "unsupported JVM: $(java -version 2>&1 | head -1)"; exit 1
fi
exec $JAVA -cp /app/classes App

ونشغله في الخدمتين:

for c in lab-java-oracle lab-java-temurin; do
  docker cp start.sh $c:/tmp/start.sh
  echo "== $c"; docker exec $c sh /tmp/start.sh | head -2
done

والمخرج سوف يكون كما يلي:

== lab-java-oracle
sorted   = [INV-1, INV-2, INV-3]
total    = SAR1,824.50
== lab-java-temurin
unsupported JVM: openjdk version "25.0.4.1" 2026-08-18 LTS

والسبب أن Oracle JDK يطبع java version بينما كل نسخ OpenJDK تطبع openjdk version، فالسكربت رفض نسخة سليمة تماماً. وإذا حذفت هذا الفحص فسوف يتوقف السكربت عند المسار الثابت:

docker exec lab-java-temurin sh -c '/usr/java/jdk-25/bin/java -version'; echo "exit=$?"
docker exec lab-java-temurin sh -c 'echo $JAVA_HOME; command -v java'

والمخرج سوف يكون كما يلي:

sh: 1: /usr/java/jdk-25/bin/java: not found
exit=127
/opt/java/openjdk
/opt/java/openjdk/bin/java

لذلك قبل النقل ابحث في سكربتاتك وملفات الإعداد عن ثلاثة أشياء: نص java version أو أي قراءة لمخرج java -version، والمسارات الثابتة مثل /usr/java بدل $JAVA_HOME، وأي فحص للخاصية java.vendor في الكود أو في أدوات المراقبة، فقد رأينا أنها تتغير من Oracle Corporation إلى Eclipse Adoptium.

بعد النقل هناك فرصة إضافية: تطبيقك لا يحتاج كل JDK، فالأداة jdeps تقرأ ملف JAR وتخبرك بالوحدات Modules التي يستخدمها، والأداة jlink تبني Runtime فيه هذه الوحدات فقط. ولاحظ أن الاثنتين جزء من OpenJDK نفسه، وأن صفحة أسئلة Oracle تذكر أن رخصة NFTC تشمل ال Runtimes التي تبنيها بـ jlink، فالأمر نفسه يعمل على أي نسخة. ولنقس ذلك أولاً داخل Container من Temurin:

docker run --rm --name lab-java-jlink -v "$(pwd)/app:/src:ro" eclipse-temurin:25-jdk bash -c '
cd /tmp && javac -d classes /src/App.java && jar --create --file app.jar --main-class App -C classes . &&
echo "### jdeps"; jdeps --print-module-deps --ignore-missing-deps app.jar;
echo "### full JDK"; du -sh /opt/java/openjdk;
jlink --add-modules $(jdeps --print-module-deps --ignore-missing-deps app.jar) --output /tmp/rt-plain;
jlink --add-modules $(jdeps --print-module-deps --ignore-missing-deps app.jar) --strip-debug --no-man-pages --no-header-files --compress=zip-9 --output /tmp/rt-small;
echo "### runtimes"; du -sh /tmp/rt-plain /tmp/rt-small; /tmp/rt-small/bin/java --list-modules; /tmp/rt-small/bin/java -jar app.jar | head -3'

والمخرج سوف يكون كما يلي:

### jdeps
java.base,jdk.httpserver
### full JDK
252M	/opt/java/openjdk
### runtimes
61M	/tmp/rt-plain
43M	/tmp/rt-small
[email protected]
[email protected]
sorted   = [INV-1, INV-2, INV-3]
total    = SAR1,824.50
sha256   = 8f7e10d9a19b4b1d8ffb8edb7e759a154a5007f9aa27f7171339d2e01b83ad7e

في المخرج أعلاه لاحظ التالي:

  • الخدمة تحتاج وحدتين فقط: java.base التي تحتاجها كل برامج Java، وjdk.httpserver بسبب السيرفر، بينما يعرض الأمر java --list-modules في JDK الكامل 69 وحدة.
  • JDK الكامل 252MB، وال Runtime بالوحدتين 61MB، ومع حذف معلومات التصحيح Debug والضغط 43MB، والنتيجة نفسها كما رأيت في الأسطر الأخيرة.
  • الخيار --ignore-missing-deps مهم مع المكتبات الخارجية، ولكنه يخفي الاعتماديات التي لم يجدها، فاختبر ال Runtime الناتج دائماً قبل أن تعتمد عليه، وإذا كان تطبيقك يحمل كلاسات بالانعكاس Reflection فقد لا يراها jdeps فتضيف وحداتها يدوياً.

والآن نضع ذلك في Dockerfile من مرحلتين Multi-stage، الأولى تبني على Temurin والثانية تحمل ال Runtime فقط على Debian صغيرة:

# Stage 1: build the app and a runtime that holds only the modules it needs
FROM eclipse-temurin:25.0.4.1_1-jdk AS build
WORKDIR /app
COPY app/App.java .
RUN javac -d classes App.java && jar --create --file app.jar --main-class App -C classes .
RUN jdeps --print-module-deps --ignore-missing-deps app.jar > modules.txt
RUN jlink --add-modules $(cat modules.txt) --strip-debug --no-man-pages --no-header-files \
          --compress=zip-9 --output /runtime

# Stage 2: a small base with the custom runtime only
FROM debian:13-slim
COPY --from=build /runtime /opt/java
COPY --from=build /app/app.jar /app/app.jar
ENV PATH=/opt/java/bin:$PATH
CMD ["java", "-jar", "/app/app.jar", "serve"]
docker build -f Dockerfile.jlink -t lab-java-app:jlink .
docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep lab-java-app

والمخرج سوف يكون كما يلي:

lab-java-app:jlink 185MB
lab-java-app:temurin 597MB
lab-java-app:oracle 1.12GB

ثم نتأكد أن ال Image الصغيرة تعمل وتعطي النتيجة نفسها، وأن ال Runtime داخلها هو Temurin بحجم 43MB:

docker run --rm --name lab-java-jlinkrun lab-java-app:jlink java -jar /app/app.jar | head -3
docker run --rm --name lab-java-jlinkrun2 lab-java-app:jlink sh -c 'du -sh /opt/java; java -version 2>&1 | head -2'

والمخرج سوف يكون كما يلي:

sorted   = [INV-1, INV-2, INV-3]
total    = SAR1,824.50
sha256   = 8f7e10d9a19b4b1d8ffb8edb7e759a154a5007f9aa27f7171339d2e01b83ad7e
43M	/opt/java
openjdk version "25.0.4.1" 2026-08-18 LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)

والصورة التالية تجمع الأحجام التي قسناها في التجربة:

مجموعتان من الأعمدة: مجلد Java Runtime بحجم 252MB لـ JDK كامل و61MB لـ Runtime بوحدتين و43MB بعد حذف معلومات التصحيح والضغط، وDocker Image للخدمة بحجم 1.12GB على Oracle JDK و597MB على Temurin و185MB على Debian صغيرة مع Runtime من jlink
أحجام ال Runtime وال Docker Image للخدمة نفسها في التجربة

حذف ما أنشأناه

نحذف ما أنشأناه فقط بالاسم:

docker rm -f lab-java-oracle lab-java-temurin
docker rmi lab-java-app:oracle lab-java-app:temurin lab-java-app:jlink

أخطاء شائعة

  • تحديث Oracle JDK القديم دون قراءة الرخصة: رخصة NFTC تنتهي لكل إصدار بعد سنة من صدور LTS التالي، فالتحديث الأمني نفسه قد ينقلك إلى OTN، كما يحدث مع Java 21 في أكتوبر 2026.
  • الحكم من كلمة Oracle في الملف: IMPLEMENTOR="Oracle Corporation" يظهر في Oracle JDK وفي Oracle OpenJDK المفتوحة، والفرق في java.runtime.name وفي السطر SOURCE.
  • جرد السيرفرات ونسيان ال Images والأجهزة: أغلب Java المجهول يأتي داخل منتجات أخرى، ومقياس الموظف يجعل أجهزة المكاتب جزءاً من الحساب.
  • استخدام Oracle OpenJDK من jdk.java.net في الإنتاج لسنوات: رخصتها مفتوحة، ولكن تحديثاتها تتوقف بعد ستة أشهر من كل إصدار.
  • الاعتماد على latest في ال Image: ثبت الإصدار الكامل مثل eclipse-temurin:25.0.4.1_1-jdk حتى لا ينتقل تطبيقك إلى إصدار آخر في البناء القادم.
  • سكربتات تقرأ java -version: نسخ OpenJDK تطبع openjdk version، فافحص سكربتاتك قبل النقل كما رأينا.

الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

  • Java مجانية: OpenJDK مفتوح برخصة GPLv2 مع استثناء Classpath، وتبني منه Eclipse وAmazon وMicrosoft وAzul وRed Hat وBellSoft نسخاً مجانية تعطيك التطبيق نفسه.
  • الذي تغيرت رخصته هو Oracle JDK: رخصة BCL قبل 16 أبريل 2019، ثم OTN التي لا تسمح بالإنتاج، ثم NFTC منذ سبتمبر 2021 لكن لفترة محددة لكل إصدار: 17 حتى 17.0.12، و21 حتى سبتمبر 2026، و25 حتى سبتمبر 2028.
  • الاشتراك Java SE Universal Subscription منذ 23 يناير 2023 يحسب على كل الموظفين والمتعاقدين، وليس على من يستخدم Java، فشركة من 3,000 موظف تدفع 378,000 دولار في السنة بالسعر المنشور.
  • اختر بحسب البيئة: Corretto على AWS، وMicrosoft على Azure، وRed Hat على RHEL، وTemurin كخيار محايد، وعقد دعم مدفوع إذا احتجت مستوى خدمة.
  • اعرف ما عندك أولاً: -XshowSettings:properties وملف release وjcmd VM.version، وسكربت يفحص ال Images دون تشغيلها.
  • النقل في تجربتنا لم يغير نتيجة واحدة، والذي انكسر هو سكربت التشغيل الذي يقرأ java -version ويستخدم مساراً ثابتاً.
  • jlink خفض ال Runtime من 252MB إلى 43MB لخدمة تحتاج وحدتين فقط.
دليلقصة Java من Sun إلى Oracle، وكيف بقيت مفتوحةكيف فتحت Sun كود Java ثم باعتها، وماذا حدث مع Oracle وGoogle في المحاكم، وكيف أصبحت Java تصدر كل ستة أشهر.دليلاللغة شيء والمترجم شيء آخر: من يقرر مستقبل لغات البرمجة؟لماذا لا تصح جمل مثل «Python بطيئة»، وما الفرق بين مواصفة اللغة والبرامج التي تطبقها، ومن يقرر مستقبلها.دليلمشاريع مؤسسة Eclipse: من Jakarta EE إلى Mosquittoما الذي تستضيفه مؤسسة Eclipse غير المحرر الذي تعرفه، ولماذا تغيرت حزم Java من javax إلى jakarta.دليلأدوات البناء Build Tools وماذا يحدث عندما تبني مشروعكما الذي يحدث بين ضغطة البناء والملف الناتج، ولماذا ينجح البناء على جهازك ويفشل على سيرفر CI، ومتى يعطيك نفس النتيجة مرتين.دليلما هي البرمجيات مفتوحة المصدر؟ القصة وأشهر الرخصكيف بدأت البرمجيات الحرة والمفتوحة، وما الفرق بين الحر والمجاني، وكيف تعرف ما تسمح به رخصة أي مشروع قبل أن تبني عليه.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال من صفحات Oracle الرسمية (أسئلة الترخيص، وخطة الدعم المحدثة في 15 سبتمبر 2026، وقائمة أسعار الاشتراك المؤرخة في 8 أكتوبر 2026) وصفحات Adoptium وAmazon وMicrosoft وAzul وRed Hat وBellSoft، وتنفيذ التجربة على Docker 29.8.0 مع Oracle JDK 25.0.4.1 وTemurin 25.0.4.1 وDocker Images الإصدار 21 من الموردين الستة.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.