
জাভা শেখার শুরুতে সবচেয়ে বেশি হতাশা তৈরি করে কনসোলের লাল রঙের কয়েকটা লাইন। প্রোগ্রাম লিখলেন, রান করলেন, আর ভেসে উঠল Exception in thread main java.lang.NullPointerException। অনেকে এই জায়গায় এসে কোড এলোমেলোভাবে ঘাঁটতে শুরু করেন, কেউ আবার পুরো কোডের চারপাশে try catch বসিয়ে এররটা চোখের আড়াল করে ফেলেন। কিন্তু সমস্যাটা তখনো রয়ে যায়, শুধু আর দেখা যায় না। কিছুদিন পর সেই লুকানো সমস্যাই বড় আকারে ফিরে আসে।
সত্যি বলতে গেলে ওই লাল লেখাগুলোই জাভার সবচেয়ে সাহায্যকারী অংশ। সেখানে হুবহু লেখা থাকে কোন ক্লাসের কোন লাইনে সমস্যা হয়েছে এবং কোন মেথড কাকে ডেকেছিল। এই টিউনে তত্ত্ব মুখস্থ করার বদলে আমরা ইচ্ছা করে পাঁচটি কমন এরর তৈরি করব, প্রতিটির স্ট্যাক ট্রেস লাইন ধরে পড়ব, তারপর কোড ঠিক করব। শেষে জানব try catch finally এবং try with resources এর সঠিক ব্যবহার কোথায়, আর নিজের Custom Exception কীভাবে বানাতে হয়।
প্রোগ্রাম চলার সময় অস্বাভাবিক কিছু ঘটলে JVM একটি অবজেক্ট তৈরি করে, যার নাম Exception। এই অবজেক্টটি যে মেথডে সমস্যা হয়েছে সেখান থেকে উপরের দিকে উঠতে শুরু করে, অর্থাৎ যে মেথড তাকে ডেকেছিল তার কাছে যায়। সেখানেও ধরার ব্যবস্থা না থাকলে আরও উপরে ওঠে। কোথাও ধরা না পড়লে থ্রেডটি বন্ধ হয়ে যায় এবং কনসোলে পুরো ভ্রমণপথ ছাপা হয়। এই ছাপা তালিকাটির নামই স্ট্যাক ট্রেস।

উপরের চিত্রটি একবার ভালো করে দেখে নিলে পরের অংশ গুলো অনেক সহজ লাগবে। সবার উপরে বসে আছে Throwable, অর্থাৎ জাভায় যা কিছু ছুঁড়ে দেওয়া যায় তার সবকিছুই ঘুরে ফিরে এর নিচে পড়ে। ঠিক তার নিচেই দুটি আলাদা ডাল, একটির নাম Error আর অন্যটির নাম Exception। এই দুটির তফাত না বুঝে অনেকেই সব কিছু ধরার চেষ্টা করেন, ফলে প্রোগ্রামের আসল সমস্যা ঢাকা পড়ে যায়।
বাম দিকের ডালটির দিকে তাকান। এখানে যা আছে সেগুলো সাধারণত প্রোগ্রামারের ভুল নয়, বরং পুরো পরিবেশের সমস্যা। মেমোরি শেষ হয়ে গেলে বা কোনো মেথড নিজেকে অসীম বার ডাকতে থাকলে এই জাতের সমস্যা আসে। এগুলো ধরার চেষ্টা করে লাভ নেই, কারণ ধরে ফেললেও প্রোগ্রামটি আর ভরসা করার মতো অবস্থায় থাকে না। এখানে করণীয় হলো কোডের গঠন বদলানো, ধরা নয়।
ডান দিকের ডালটিই আমাদের আসল কাজের জায়গা। এর ভেতরে আবার দুটি ভাগ আছে এবং এই ভাগটিই সবচেয়ে বেশি বিভ্রান্তি তৈরি করে।
এই বংশতালিকা জানার সবচেয়ে বড় ব্যবহারিক লাভ হলো catch লেখার ক্রম বোঝা। যদি একই সাথে অনেক গুলো catch লেখেন, তাহলে নিচের দিকের অর্থাৎ নির্দিষ্ট জাতটি আগে লিখতে হবে আর উপরের দিকের অর্থাৎ সাধারণ জাতটি পরে। উল্টো করে লিখলে কম্পাইলার সোজা বলে দেবে যে পরের catch টিতে কখনোই ঢোকা যাবে না। কারণটি এখন পরিষ্কার, বড় জাতটি আগে সব কিছু ধরে নিয়ে চলে যায়।
নিচের স্ট্যাক ট্রেসটি মন দিয়ে দেখুন। এটি পড়তে হয় উপর থেকে নিচে, আর কাজের তথ্যটুকু থাকে প্রথম দুই লাইনেই।
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
at com.example.ErrorLab.printLength(ErrorLab.java:16)
at com.example.ErrorLab.main(ErrorLab.java:6)

উপরের চিত্রে একটি সত্যিকারের স্ট্যাক ট্রেস আর তার পাশে পড়ার নিয়মটি একসাথে রাখা হয়েছে। লাল রঙের অনেক গুলো লাইন দেখে শুরুতে মনে হয় সব শেষ, অথচ কাজে লাগে মূলত তিনটি লাইন। বাকি লাইন গুলো শুধু বলে দেয় কে কাকে ডেকেছিল, অর্থাৎ সমস্যার জায়গায় পৌঁছানোর রাস্তাটি কেমন ছিল।
প্রথম লাইনটি জানায় কোন জাতের সমস্যা হয়েছে। দ্বিতীয় লাইনটি নতুন জাভার একটি বড় উপহার, এটি সরাসরি নাম ধরে বলে দেয় ঠিক কোন জিনিসটি ফাঁকা ছিল। আগের ভার্সন গুলোতে শুধু জাতের নামটুকু পাওয়া যেত, তাই এই লাইনটি পেলে আলাদা করে খোঁজাখুঁজির দরকারই পড়ে না।
তৃতীয় গুরুত্বপূর্ণ জায়গা হলো প্রথম at দিয়ে শুরু হওয়া লাইনটি। এখানেই আসল ঘটনাটি ঘটেছে এবং সাথে ফাইলের নাম ও লাইন নম্বর দুটোই লেখা থাকে। একটি অভ্যাস গড়ে তুললে অনেক সময় বাঁচবে, উপর থেকে নিচে পড়তে গিয়ে প্রথম যে লাইনটিতে আপনার নিজের প্যাকেজের নাম দেখবেন সেখানেই থামুন। তার উপরের লাইন গুলো সাধারণত জাভার নিজের বা কোনো লাইব্রেরির ভেতরের কথা, সেগুলো নিয়ে মাথা ঘামানোর দরকার নেই।
আরও দুটি বিষয় মনে রাখা ভালো।
যা করা উচিত নয় সেটিও বলে রাখা দরকার। স্ট্যাক ট্রেস দেখে ঘাবড়ে গিয়ে পুরো অংশটি catch দিয়ে ঢেকে দেওয়ার চেষ্টা করবেন না। এতে সমস্যা মেটে না, শুধু চোখের আড়ালে চলে যায় এবং পরে আরও বড় আকারে ফিরে আসে।
নিচের কোডটি ErrorLab.java নামে সেভ করে রান করুন। এখানে findUser মেথডটি ব্যবহারকারী না পেলে null ফেরত দিচ্ছে, আর সেই null এর উপরেই length মেথড ডাকা হচ্ছে।
public class ErrorLab {
public static void main(String[] args) {
String name = findUser(101);
printLength(name);
}
static String findUser(int id) {
if (id == 100) {
return "Nasirul";
}
return null;
}
static void printLength(String name) {
System.out.println(name.length());
}
}
static void printLength(String name) {
if (name == null) {
System.out.println("ইউজার পাওয়া যায়নি");
return;
}
System.out.println(name.length());
}
অ্যারের ঘর গোনা শুরু হয় শূন্য থেকে, তাই তিন ঘরের অ্যারের সর্বশেষ ইনডেক্স দুই। নিচের লুপে সমান চিহ্ন ব্যবহার করার কারণে প্রোগ্রামটি চার নম্বর ঘরে হাত দিতে চাইছে।
int[] marks = {70, 85, 90};
for (int i = 0; i <= marks.length; i++) {
System.out.println(marks[i]);
}
ব্যবহারকারীর দেওয়া লেখা সংখ্যায় রূপ দিতে গিয়ে এই এররটি সবচেয়ে বেশি ঘটে। লেখাটির ভেতরে একটি অক্ষর বা ফাঁকা জায়গা থাকলেই parseInt আর কাজ করে না।
String input = "12a"; int value = Integer.parseInt(input); System.out.println(value);
try {
int value = Integer.parseInt(input.trim());
System.out.println("সংখ্যাটি " + value);
} catch (NumberFormatException e) {
System.out.println("শুধু সংখ্যা লিখুন, আপনি লিখেছেন " + input);
}
পূর্ণসংখ্যাকে শূন্য দিয়ে ভাগ করলে জাভা ArithmeticException ছোড়ে। মজার বিষয় হলো দশমিক সংখ্যার ক্ষেত্রে এররের বদলে Infinity আসে, তাই গড় হিসাবের কোডে ভুলটি অনেক সময় চোখে পড়ে না।
int total = 250; int count = 0; int average = total / count;
এবার ClassCastException দেখুন। একটি অবজেক্টকে জোর করে ভুল টাইপে রূপ দিতে চাইলে এই এররটি আসে।
Object obj = "Techtunes"; Integer number = (Integer) obj;
if (obj instanceof Integer number) {
System.out.println(number + 10);
} else {
System.out.println("এটি সংখ্যা নয়");
}
finally ব্লকের কোড এররে হোক বা না হোক চলবেই, তাই আগে এখানে ফাইল বা ডেটাবেজ বন্ধ করার কাজ লেখা হতো। এখন সেই কাজটি try with resources নিজেই করে দেয়, ফলে বন্ধ করতে ভুলে যাওয়ার ঝুঁকি থাকে না।
try (BufferedReader reader = new BufferedReader(new FileReader("marks.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("ফাইল পড়া যায়নি, কারণ " + e.getMessage());
}
প্রজেক্ট বড় হলে নিজের নিয়ম অনুযায়ী Exception বানালে কোড পড়তে সুবিধা হয়। নিচের উদাহরণে নম্বর যাচাইয়ের জন্য একটি আলাদা Exception তৈরি করা হয়েছে।
class InvalidMarkException extends RuntimeException {
public InvalidMarkException(String message) {
super(message);
}
}
static void checkMark(int mark) {
if (mark < 0 || mark > 100) {
throw new InvalidMarkException("নম্বর ০ থেকে ১০০ এর মধ্যে হতে হবে, পাওয়া গেছে " + mark);
}
System.out.println("নম্বর ঠিক আছে");
}
নিচের তালিকায় শুরুর দিকে সবচেয়ে বেশি যে গুলোর মুখোমুখি হতে হয় সেগুলো এক জায়গায় সাজানো হলো। কোনটি দেখলে কোথায় তাকাতে হবে, সেটি একবার মাথায় বসে গেলে সমাধানের সময় অনেক কমে যায়।

তালিকাটি লক্ষ করলে একটি বিষয় ধরা পড়বে, ডান কলামের সমাধান গুলোর প্রায় সবই ঘটনার আগে করার কাজ, পরে নয়। অর্থাৎ আসল দক্ষতা হলো ধরা নয়, বরং এমন ভাবে কোড লেখা যাতে সমস্যাটি তৈরিই না হয়। ভাগ করার আগে শর্ত দিয়ে থামানো বা লুপ লেখার সময় দৈর্ঘ্যের সাথে মিলিয়ে নেওয়া, এই ছোট অভ্যাস গুলোই বড় ভুল আটকে দেয়।
catch এর ভেতরে কী লিখবেন সেটি নিয়েও একটি কথা বলা দরকার। শুধু এররটি ছাপিয়ে দিয়ে চুপ করে থাকলে ব্যবহারকারী কিছুই বুঝবে না, আর কেবল লেখা ছাপালে পরে সমস্যা খুঁজে বের করাও কঠিন হয়ে যায়। তাই catch এর ভেতরে অন্তত দুটি কাজ রাখুন, সমস্যাটি কোথাও লিখে রাখা এবং ব্যবহারকারীকে বাংলায় বোধগম্য একটি বার্তা দেখানো।
শেষে একটি সতর্কবার্তা। কখনোই খালি catch লিখে রেখে দেবেন না। ভেতরে কিছু না লিখলে প্রোগ্রামটি এমন ভাব করবে যেন সব ঠিক আছে, অথচ ভেতরে কাজটি হয়নি। এই ধরনের ভুল ধরা সবচেয়ে কঠিন, কারণ কোথাও কোনো চিহ্নই থাকে না।
এমন হলে তালিকার নিচের দিকে তাকান এবং প্যাকেজের নাম দিয়ে খুঁজুন। লাইব্রেরির ভেতরের লাইনগুলো বাদ দিয়ে নিজের প্যাকেজের প্রথম যে লাইনটি পাবেন, ভুলটি সেখানেই আছে।
catch ব্লকে যে Exception ধরেছেন সেটি হয়তো আসল Exception এর মূল ক্লাস নয়। কনসোলের লেখা নাম হুবহু মিলিয়ে নিন, প্রয়োজনে একই try এর সাথে একাধিক catch ব্লক ব্যবহার করুন।
এডিটরে ব্রেকপয়েন্ট বসিয়ে ডিবাগ মোডে চালান এবং Variables প্যানেলে দেখুন কোন রেফারেন্সের মান null। IntelliJ IDEA তে Run মেনু থেকে ভিউ প্যানেলে Exception Breakpoint যোগ করলে এরর ঘটার ঠিক মুহূর্তেই প্রোগ্রাম থেমে যায়।
এতক্ষণ ধরে যে কাজ গুলো হাতে কলমে করা হলো, সেগুলো শেখার জন্য যথেষ্ট। কিন্তু সত্যিকারের প্রজেক্টে কাজ করতে গেলে আরও কিছু অভ্যাস দরকার হয়, যেগুলো শুরুতেই গড়ে নিলে পরে অনেক ঝামেলা থেকে বাঁচবেন।
একটি ভুল ধারণা এখানে ভেঙে দেওয়া দরকার। অনেকে মনে করেন বেশি বেশি try catch লিখলে প্রোগ্রাম বেশি নিরাপদ হয়। বাস্তবে উল্টোটা ঘটে। প্রয়োজনের বেশি জায়গায় ধরে রাখলে কোড পড়া কঠিন হয়ে যায় এবং আসল সমস্যা গুলো চাপা পড়ে যায়।
তাই একটি সহজ মাপকাঠি মনে রাখুন। ধরার পর যদি আপনার হাতে করার মতো কিছু থাকে, তবেই ধরুন। যেমন অন্য একটি পথ ধরে চেষ্টা করা, ব্যবহারকারীকে আবার লিখতে বলা, বা কাজটি পরে করার জন্য জমা রাখা। আর যদি করার মতো কিছুই না থাকে, তাহলে সেটিকে উপরে যেতে দিন, যেখানে সিদ্ধান্ত নেওয়ার মতো তথ্য আছে সেখানে সামলানো হবে।
একটি শেষ পরামর্শ দিয়ে এই অংশটি শেষ করি। যতবার কোনো নতুন এররের মুখোমুখি হবেন, সেটির নাম আর সমাধানটি নিজের একটি খাতায় বা নোট অ্যাপে লিখে রাখুন। প্রথম মাসে তালিকাটি বড় হতে থাকবে, কিন্তু দুই তিন মাস পরে দেখবেন নতুন কিছু যোগ করার দরকারই পড়ছে না। কারণ হাতে গোনা কয়েকটি ভুলই ঘুরে ফিরে আসে, আর সেগুলো একবার মুখস্থ হয়ে গেলে আপনি নিজেই বুঝে যাবেন কোথায় সমস্যা হয়েছে।
Exception আপনার শত্রু নয়, বরং সবচেয়ে সৎ পরামর্শদাতা। যে মুহূর্তে আপনি স্ট্যাক ট্রেসের প্রথম দুই লাইন পড়তে শিখে যাবেন, সেই মুহূর্ত থেকে ডিবাগিংয়ের সময় কয়েক গুণ কমে যাবে। আজ যে পাঁচটি এরর নিয়ে কাজ করলাম, সেগুলো ইচ্ছা করে আরও কয়েকবার তৈরি করুন এবং প্রতিবার স্ট্যাক ট্রেস পড়ে নিজে নিজে জায়গাটি বের করার অভ্যাস গড়ুন।
আপনি জাভা শিখতে গিয়ে কোন এররটিতে সবচেয়ে বেশি সময় নষ্ট করেছেন, টিউমেন্টে জানান। আজই নিজের কোডে এই পাঁচটি এরর ইচ্ছা করে তৈরি করে স্ট্যাক ট্রেস পড়ার অভ্যাসটি গড়ে তুলুন, কয়েক দিনেই পার্থক্যটা টের পাবেন।
আমি নাছিরুল হক। বিশ্বের সর্ববৃহৎ বিজ্ঞান ও প্রযুক্তির সৌশল নেটওয়ার্ক - টেকটিউনস এ আমি 1 সপ্তাহ 5 দিন যাবৎ যুক্ত আছি। টেকটিউনস আমি এ পর্যন্ত 24 টি টিউন ও 3 টি টিউমেন্ট করেছি। টেকটিউনসে আমার 0 ফলোয়ার আছে এবং আমি টেকটিউনসে 2 টিউনারকে ফলো করি।
আমি নাছিরুল হক। প্রোগ্রামিং আর প্রযুক্তি নিয়ে ঘাঁটাঘাঁটি করতে ভালোবাসি। টেকটিউনসে মূলত Java প্রোগ্রামিং নিয়ে ধাপে ধাপে হাতে কলমে টিউটোরিয়াল লিখি — JDK ইনস্টল ও এনভায়রনমেন্ট সেটআপ থেকে শুরু করে IntelliJ IDEA, Exception Handling, OOP, Java Swing ও ফাইল হ্যান্ডলিং পর্যন্ত। এছাড়া স্মার্টফোন, ব্যাটারি ও নেটওয়ার্ক নিয়ে সহজ ভাষায় ব্যাখ্যামূলক...