> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# هل Java مجانية؟ أي JDK تستخدم في شركتك
- URL: https://arabroot.io/articles/هل-java-مجانية-واي-jdk-تستخدم-في-شركتك/
- Published: 2026-10-12T13:30:00.000Z
- Updated: 2026-10-14T07:41:34.000Z
- Description: متى تحتاج اشتراكاً في Oracle JDK ومتى لا تحتاجه، وكيف تعرف نسخ Java التي تعمل عندك وتنقلها إلى نسخة مجانية بأمان.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, أساسيات الحاسب والبرمجة, سياسات المصادر المفتوحة, السيادة الرقمية والامتثال, الاستضافة الذاتية

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

وقد شرحنا الفرق بين مواصفة Java والتطبيق المرجعي OpenJDK والتوزيعات المبنية منه في قسم [Java في مقال مواصفات لغات البرمجة](https://arabroot.io/articles/%D9%85%D9%88%D8%A7%D8%B5%D9%81%D8%A7%D8%AA-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AD%D9%88%D9%83%D9%85%D8%AA%D9%87%D8%A7/#java)، ورأينا هناك أن `java -version` يعطي السطر الأول نفسه في Temurin وCorretto وMicrosoft، وحكينا قصة Java من Sun إلى Oracle في مقال [قصة Java من Sun إلى Oracle](https://arabroot.io/articles/%D9%82%D8%B5%D8%A9-java-%D9%85%D9%86-sun-%D8%A7%D9%84%D9%89-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 مع الاستثناء](https://arabroot.io/content/images/2026/10/java-licensing-01-builds.webp)

كود واحد في OpenJDK، ونسخ كثيرة تختلف في الرخصة ومدة التحديثات

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

- **Oracle JDK:** منتج Oracle التجاري، تنزله من oracle.com/java، ورخصته اليوم إما NFTC أو OTN بحسب الإصدار وتاريخ التحديث كما سيأتي، وهو الوحيد في الصورة الذي قد تحتاج اشتراكاً لتشغيله في الإنتاج.
- **Oracle OpenJDK:** بناء من Oracle على jdk.java.net برخصة GPLv2 مع الاستثناء، مجاني للجميع، ولكن [صفحة أسئلة Oracle](https://www.oracle.com/java/technologies/javase/jdk-faqs.html?ref=arabroot.io) تبين أن تحديثاته تتوقف بعد ستة أشهر من كل إصدار، فـ 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](https://www.oracle.com/downloads/licenses/javase-license1.html?ref=arabroot.io)، وتاريخ آخر تحديث في نصها 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](https://www.oracle.com/downloads/licenses/no-fee-license.html?ref=arabroot.io) واختصاراً NFTC، وهي تسمح بالاستخدام دون مقابل لكل المستخدمين حتى في الإنتاج وفي العمليات الداخلية للشركة، وتسمح بإعادة التوزيع بشرط ألا تأخذ رسوماً مقابلها، وتشمل بحسب صفحة الأسئلة ال Runtimes التي تبنيها بأداة jlink. ولكن النقطة التي يغفل عنها كثيرون هي أن NFTC لا تبقى مع الإصدار طوال حياته، فبحسب صفحة الأسئلة و[خطة الدعم](https://www.oracle.com/java/technologies/java-se-support-roadmap.html?ref=arabroot.io) المحدثة في 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](https://arabroot.io/content/images/2026/10/java-licensing-02-oracle-licenses.webp)

رخصة Oracle JDK لكل إصدار طويل الدعم بحسب تاريخ التحديث

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

إذا احتجت Oracle JDK في الإنتاج تحت OTN فالطريق الرسمي هو الاشتراك، وتقول [صفحة أسئلة الاشتراك](https://www.oracle.com/java/technologies/java-se-subscription-faq.html?ref=arabroot.io) إن Java SE Universal Subscription حل محل الاشتراكات القديمة Java SE Subscription وJava SE Desktop Subscription منذ 23 يناير 2023، وإنه يباع بمقياس الموظف Employee، وإن طريقة النشر والبنية لا تؤثر في السعر. والمهم هنا هو تعريف الموظف، فبحسب [قائمة الأسعار](https://www.oracle.com/us/corporate/pricing/price-lists/java-se-subscription-pricelist-5028356.pdf?ref=arabroot.io) المؤرخة في 8 أكتوبر 2026 فهو كل موظفيك بدوام كامل أو جزئي أو مؤقت، مع كل موظفي الوكلاء والمقاولين والمتعاقدين الخارجيين والمستشارين الذين يدعمون عملياتك الداخلية، والقائمة تقول بوضوح إن العدد يحسب «بغض النظر عن عدد الموظفين الذين يستخدمون البرامج».

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

| عدد الموظفين         | السعر المنشور                                                    |
| -------------------- | ---------------------------------------------------------------- |
| حتى 10               | 100 دولار في السنة                                               |
| من 11 إلى 100        | 1,000 دولار في السنة                                             |
| من 101 إلى 500       | 10,000 دولار في السنة                                            |
| من 501 إلى 2,999     | 10,000 دولار في السنة، و147.20 دولاراً في السنة لكل موظف فوق 500 |
| من 3,000 إلى 9,999   | 10.50 دولار للموظف في الشهر                                      |
| من 40,000 إلى 49,999 | 5.25 دولار للموظف في الشهر، وأكثر من 50,000 بالتواصل             |

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

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

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

| النسخة                                                                                               | من يبنيها                       | الرخصة   | معنى التاريخ                  | 8       | 11            | 17      | 21      | 25      |
| ---------------------------------------------------------------------------------------------------- | ------------------------------- | -------- | ----------------------------- | ------- | ------------- | ------- | ------- | ------- |
| [Eclipse Temurin](https://adoptium.net/support/?ref=arabroot.io)                                     | مشروع Adoptium في مؤسسة Eclipse | GPLv2+CE | آخر إتاحة، «على الأقل»        | 12/2030 | 10/2027       | 10/2027 | 12/2029 | 9/2031  |
| [Amazon Corretto](https://aws.amazon.com/corretto/faqs/?ref=arabroot.io)                             | Amazon                          | GPLv2+CE | نهاية الدعم المجاني           | 12/2030 | 1/2032        | 10/2029 | 10/2030 | 10/2032 |
| [Microsoft Build of OpenJDK](https://learn.microsoft.com/en-us/java/openjdk/support?ref=arabroot.io) | Microsoft                       | GPLv2+CE | أقرب نهاية للدعم              | لا يوجد | 9/2027        | 9/2027  | 9/2028  | 9/2030  |
| [Azul Zulu](https://www.azul.com/products/azul-support-roadmap/?ref=arabroot.io)                     | Azul                            | GPLv2+CE | نهاية الدعم التجاري Azul Core | 12/2030 | 1/2032        | 9/2029  | 9/2031  | 9/2033  |
| [Red Hat build of OpenJDK](https://access.redhat.com/articles/1299013?ref=arabroot.io)               | Red Hat                         | GPLv2+CE | نهاية الدعم الكامل مع RHEL    | 11/2026 | انتهى 10/2024 | 12/2027 | 12/2029 | 12/2030 |
| [BellSoft Liberica](https://bell-sw.com/roadmap/?ref=arabroot.io)                                    | BellSoft                        | GPLv2+CE | نهاية الدعم التجاري           | 3/2031  | 3/2032        | 3/2030  | 3/2032  | 3/2034  |

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

- **Temurin:** تقول [أسئلة Adoptium](https://adoptium.net/docs/faq/?ref=arabroot.io) إن Temurin مجاني للاستخدام «إلى الأبد» برخصة GPLv2 مع استثناء Classpath، وتقول صفحة الدعم إن الإصدارات طويلة الدعم تدعم أربع سنوات على الأقل، وإن المشروع يستمر في بنائها ما دام الكود المصدري لها يحدث في OpenJDK. والدعم هنا هو دعم المجتمع عبر البلاغات، أما الدعم التجاري بعقد فتقدمه شركات أخرى تذكرها صفحتهم.
- **Corretto:** تقول Amazon إنه موزع برخصة مفتوحة دون أي مقابل، وإن الدعم الطويل يشمل التحديثات الأمنية وتحسينات الأداء دون مقابل حتى التاريخ المذكور، وإنه يجتاز اختبارات التوافق TCK.
- **Microsoft:** التحديثات الربعية مجانية للجميع وتستطيع نشرها في أي مكان بحسب الرخصة، أما الدعم التجاري فهو فقط لعملاء Azure على أحمال Azure، ولا توجد عندهم نسخة Java 8.
- **Azul Zulu:** تقول [وثائق Azul](https://docs.azul.com/core/?ref=arabroot.io) إن Zulu نسخ مجانية الاستخدام، والتواريخ في الجدول هي تواريخ الدعم التجاري في اشتراك Azul Core، ولم نجد في صفحات Azul العامة تاريخاً مكتوباً لتوقف التحديثات المجانية لكل إصدار، لذلك إذا اخترت Zulu دون اشتراك فتابع صفحة التنزيل بنفسك.
- **Red Hat:** الدعم مشمول في اشتراك RHEL، والدعم الممتد ELS لـ Java 11 حتى يناير 2032 مدفوع باشتراك منفصل، وفي التجربة نزلنا Docker Image الخاصة بـ OpenJDK 21 من سجل Red Hat العام دون تسجيل دخول.
- **Liberica:** تقول [صفحة Liberica](https://bell-sw.com/libericajdk/?ref=arabroot.io) إنها مجانية تماماً في الإنتاج، والتواريخ في الجدول للدعم التجاري الذي تقول 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:

```java
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
# 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
# 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 فنزل دون تسجيل دخول. الآن نبني ونشغل الخدمتين:

```bash
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
```

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

```text
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` يطبعها كلها، ونحن نأخذ منها الأسطر التي تهمنا:

```bash
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:

```text
    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:

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

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

```text
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` تعرض العمليات ثم تسأل كل واحدة عن نسختها:

```bash
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
```

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

```text
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` إلى الخارج ويقرؤه:

```bash
#!/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 كانت موجودة على جهاز التجربة:

```bash
./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
```

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

```text
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`:

```bash
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) '
```

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

```text
    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 تعيده كل ربع سنة، وقد شرحنا في مقال [أدوات البناء](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-build-tools-%D9%85%D9%86-make-%D8%A7%D9%84%D9%89-maven-%D9%88-gradle-%D9%88-dotnet/) لماذا تثبت رقم الإصدار الكامل في ملف البناء حتى لا يتغير ما تحت تطبيقك دون علمك.

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

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

```bash
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"
```

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

```text
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`، ويستخدم مساراً ثابتاً:

```bash
#!/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
```

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

```bash
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
```

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

```text
== 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`، فالسكربت رفض نسخة سليمة تماماً. وإذا حذفت هذا الفحص فسوف يتوقف السكربت عند المسار الثابت:

```bash
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'
```

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

```text
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`.

### Runtime صغير بأداتي jdeps وjlink

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

```bash
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'
```

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

```text
### jdeps
java.base,jdk.httpserver
### full JDK
252M	/opt/java/openjdk
### runtimes
61M	/tmp/rt-plain
43M	/tmp/rt-small
java.base@25.0.4.1
jdk.httpserver@25.0.4.1
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 صغيرة:

```dockerfile
# 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"]
```

```bash
docker build -f Dockerfile.jlink -t lab-java-app:jlink .
docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep lab-java-app
```

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

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

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

```bash
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'
```

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

```text
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](https://arabroot.io/content/images/2026/10/java-licensing-03-sizes.webp)

أحجام ال Runtime وال Docker Image للخدمة نفسها في التجربة

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

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

```bash
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 لخدمة تحتاج وحدتين فقط.

[![](https://arabroot.io/content/images/2026/10/java-story-cover-cover.webp)دليلقصة Java من Sun إلى Oracle، وكيف بقيت مفتوحةكيف فتحت Sun كود Java ثم باعتها، وماذا حدث مع Oracle وGoogle في المحاكم، وكيف أصبحت Java تصدر كل ستة أشهر.](https://arabroot.io/articles/%D9%82%D8%B5%D8%A9-java-%D9%85%D9%86-sun-%D8%A7%D9%84%D9%89-oracle/)[![](https://arabroot.io/content/images/2026/10/language-specs-cover-cover.webp)دليلاللغة شيء والمترجم شيء آخر: من يقرر مستقبل لغات البرمجة؟لماذا لا تصح جمل مثل «Python بطيئة»، وما الفرق بين مواصفة اللغة والبرامج التي تطبقها، ومن يقرر مستقبلها.](https://arabroot.io/articles/%D9%85%D9%88%D8%A7%D8%B5%D9%81%D8%A7%D8%AA-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AD%D9%88%D9%83%D9%85%D8%AA%D9%87%D8%A7/)[![](https://arabroot.io/content/images/2026/10/eclipse-projects-cover-cover.webp)دليلمشاريع مؤسسة Eclipse: من Jakarta EE إلى Mosquittoما الذي تستضيفه مؤسسة Eclipse غير المحرر الذي تعرفه، ولماذا تغيرت حزم Java من javax إلى jakarta.](https://arabroot.io/articles/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D8%A4%D8%B3%D8%B3%D8%A9-eclipse-%D9%85%D9%86-jakarta-ee-%D8%A7%D9%84%D9%89-mosquitto/)[![](https://arabroot.io/content/images/2026/10/build-tools-cover-cover.webp)دليلأدوات البناء Build Tools وماذا يحدث عندما تبني مشروعكما الذي يحدث بين ضغطة البناء والملف الناتج، ولماذا ينجح البناء على جهازك ويفشل على سيرفر CI، ومتى يعطيك نفس النتيجة مرتين.](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-build-tools-%D9%85%D9%86-make-%D8%A7%D9%84%D9%89-maven-%D9%88-gradle-%D9%88-dotnet/)[![](https://arabroot.io/content/images/2026/10/open-source-licenses-cover-cover.webp)دليلما هي البرمجيات مفتوحة المصدر؟ القصة وأشهر الرخصكيف بدأت البرمجيات الحرة والمفتوحة، وما الفرق بين الحر والمجاني، وكيف تعرف ما تسمح به رخصة أي مشروع قبل أن تبني عليه.](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%AA%D8%B1%D8%A7%D8%AE%D9%8A%D8%B5%D9%87%D8%A7/)

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

- أكتوبر 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 من الموردين الستة.