Appearance
POJO, Enum, and Final Classes in Java
You have already seen how to write a basic Java class. Now it is time to meet three special kinds of classes that show up everywhere in real Java projects: the POJO, the enum, and the final class. Each one solves a very specific problem. By the end of this article you will know exactly what each one does, why it exists, and how to use it confidently in your own code.
Part One: POJO
What Does POJO Actually Mean?
POJO stands for Plain Old Java Object. The name is deliberately humble. A POJO is just a normal Java class with no fancy dependencies, no framework requirements, nothing exotic. It is the simplest possible thing: a class that holds data and gives you access to it through getter and setter methods.
Think of a POJO like a plain cardboard box. You put things inside it, you take things out, and the box itself does nothing special. It does not know about databases, HTTP requests, or any specific framework. It is just a container for data.
Here is the checklist that makes a class a POJO:
- The class is
public. - It has a
publicdefault (no-argument) constructor. - It has variables and their getter and setter methods. There is no restriction on the variables' access modifiers.
- It uses no framework annotations such as
@Entityor@Table. - It does not extend any class (other than the implicit
Object) and does not implement any interface.
A Simple POJO Example
java
// A POJO representing a customer
public class Customer {
// Fields are private - nobody can access them directly
private int id;
private String name;
private String email;
// Public default constructor - no arguments needed
public Customer() {
// body can be empty
}
// Getter for id - the only way to read the id from outside
public int getId() {
return id;
}
// Setter for id - the only way to change the id from outside
public void setId(int id) {
this.id = id;
}
// Getter for name
public String getName() {
return name;
}
// Setter for name
public void setName(String name) {
this.name = name;
}
// Getter for email
public String getEmail() {
return email;
}
// Setter for email
public void setEmail(String email) {
this.email = email;
}
}This class does nothing fancy. It stores three pieces of data and lets you read and write them. That is exactly what a POJO should do.
Where Do POJOs Actually Appear in Real Projects?
You might wonder why you need a special name for something so simple. The reason is that POJOs solve a very common problem in real software projects: different parts of your application speak different languages.
Imagine you have a web service and a client sends you an HTTP request. The data arrives as JSON or XML with field names chosen by whoever designed that API. Meanwhile the rest of your application was built using its own naming conventions and data structures. You need a translation layer.
A POJO sits exactly in that translation layer. You take the incoming request data, create a POJO object, fill its fields from the request data using the setters, and then pass that POJO around to all the other parts of your application. If the external API changes its field names tomorrow, you only update the mapping code in one place. Every other class in your application continues to work with the POJO as if nothing changed.
Here is a concrete scenario. A REST API sends you a request with fields called ID and name. But your internal application uses the names customerId and customerName. You create a POJO called CustomerRequest with the internal naming, and you write one small piece of mapping code at the entry point:
java
// The POJO with your internal naming convention
public class CustomerRequest {
private int customerId;
private String customerName;
public CustomerRequest() {}
public int getCustomerId() {
return customerId;
}
public void setCustomerId(int customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}java
// Mapping code at the boundary - this is the only place that knows about
// the external field names ID and name
public CustomerRequest mapFromExternalRequest(ExternalRequest external) {
CustomerRequest pojo = new CustomerRequest();
pojo.setCustomerId(external.getID()); // external says ID, we say customerId
pojo.setCustomerName(external.getName()); // external says name, we say customerName
return pojo;
}After that mapping step, every other class in your system works with CustomerRequest and uses getCustomerId() and getCustomerName(). If the external API is redesigned and suddenly calls the field userId instead of ID, you update exactly one line in the mapping code. Everything else stays the same.
The second common place where POJOs appear is when talking to a database. You create a student POJO that represents one row in your students table. You fill the POJO from the database, pass it around your application, and eventually write it back. The POJO is the bridge between raw data storage and your application logic.
Part Two: Enum
The Problem That Enum Solves
Before Java had enums, developers faced a genuinely nasty problem when working with fixed sets of values. Imagine you are writing a system that needs to work with days of the week. A common approach was to use integer constants:
java
// Old approach before enum existed - the "magic numbers" problem
public class Day {
public static final int MONDAY = 0;
public static final int TUESDAY = 1;
public static final int WEDNESDAY = 2;
public static final int THURSDAY = 3;
public static final int FRIDAY = 4;
public static final int SATURDAY = 5;
public static final int SUNDAY = 6;
}Then you write a method that checks whether a day is a weekend:
java
// Method accepting an integer day value
public boolean isWeekend(int day) {
if (day == Day.SATURDAY || day == Day.SUNDAY) {
return true;
}
return false;
}Now here is the problem. When you call this method, you are passing an int. The Java compiler sees only a plain integer. It has absolutely no idea that day is supposed to be a value between 0 and 6 representing a specific day of the week. There is nothing stopping you or any other developer from doing this:
java
// All of these compile without any error
isWeekend(100); // 100 is not a valid day, but Java accepts it
isWeekend(-5); // Negative value, still compiles
isWeekend(999); // No error, no warningThe compiler cannot help you catch the mistake. You would only discover the bug at runtime, which is the worst possible time to find it. This is called the type safety problem. The integer type is too broad. It accepts any number, but your method only makes sense for values 0 through 6.
This is exactly the problem that enum was designed to eliminate.
Introducing Enum: A Collection of Named Constants
An enum is a special type in Java that represents a fixed collection of named constants. Here is how you declare the days of the week as an enum:
java
// Declaring an enum - the keyword is enum instead of class
public enum WeekDay {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY // the semicolon here is optional when nothing else follows
}Now you rewrite the isWeekend method to accept a WeekDay instead of an int:
java
// Method now accepts only a WeekDay - no invalid values possible
public boolean isWeekend(WeekDay day) {
if (day == WeekDay.SATURDAY || day == WeekDay.SUNDAY) {
return true;
}
return false;
}Try passing 100 or 999 to this method now. The compiler immediately refuses with a compile time error. You can only pass one of the seven named constants. The type system enforces your intention. That is type safety, and it is one of the biggest benefits of using enums.
The readability improvement is also enormous. Compare these two calls:
java
isWeekend(6); // what does 6 mean? you have to go look it up
isWeekend(WeekDay.SUNDAY); // instantly readable, no ambiguityWhat Enum Actually Is Under the Hood
When you write public enum WeekDay { ... }, Java internally converts it into a special class. You are not actually writing it, but here is what the compiler generates for you behind the scenes:
java
// This is roughly what the compiler generates from your enum declaration
// You never write this yourself - Java does it for you
public final class WeekDay extends java.lang.Enum<WeekDay> {
// Each constant becomes a public static final instance of the class
public static final WeekDay MONDAY = new WeekDay("MONDAY", 0);
public static final WeekDay TUESDAY = new WeekDay("TUESDAY", 1);
public static final WeekDay WEDNESDAY = new WeekDay("WEDNESDAY", 2);
public static final WeekDay THURSDAY = new WeekDay("THURSDAY", 3);
public static final WeekDay FRIDAY = new WeekDay("FRIDAY", 4);
public static final WeekDay SATURDAY = new WeekDay("SATURDAY", 5);
public static final WeekDay SUNDAY = new WeekDay("SUNDAY", 6);
// Private constructor - you cannot create new WeekDay instances
private WeekDay(String name, int ordinal) {
super(name, ordinal);
}
}The compiler also generates two static methods for every enum, values() and valueOf(String), which you will meet below.
Several things follow from this generated code. Each constant is implicitly public static final, so you never write those keywords yourself.
First, every enum implicitly extends java.lang.Enum. You do not write this yourself. Java adds it automatically. This is why an enum cannot extend any other class. Java does not support inheriting from multiple classes, and since java.lang.Enum is already occupying the one allowed parent slot, there is no room left. If you try to write public enum WeekDay extends SomeOtherClass, the compiler will refuse with an error.
Second, the constructor is private. This is why you cannot create an enum instance using new WeekDay(). The only instances that ever exist are the ones declared as constants inside the enum. This gives enums their defining property: a fixed, closed set of values.
No other class can extend an enum; the compiler forbids it.
An enum can implement interfaces though. The rule against extending applies to classes only. You can have public enum WeekDay implements SomeInterface and that works fine.
The Four Built In Methods Every Enum Has
Every enum has these four methods without you writing them: ordinal() and name() are inherited from java.lang.Enum, and the static values() and valueOf(String) are generated by the compiler for each enum.
values() returns an array containing all the constants in the enum, in the order they were declared. You use this when you want to loop through every constant:
java
// Loop through every day of the week
for (WeekDay day : WeekDay.values()) {
// WeekDay.values() returns [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
System.out.println(day);
}You might wonder how you can call WeekDay.values() when you never defined a values method in your enum. The compiler generates it for every enum (it is not a method of java.lang.Enum). It is static, so you call it on the class name directly.
ordinal() returns the position of the constant in the enum, starting from zero. MONDAY has ordinal 0, TUESDAY has ordinal 1, and so on up to SUNDAY with ordinal 6:
java
WeekDay day = WeekDay.FRIDAY;
System.out.println(day.ordinal()); // prints 4name() returns the name of the constant exactly as you wrote it in the declaration, as a String:
java
WeekDay day = WeekDay.FRIDAY;
System.out.println(day.name()); // prints "FRIDAY" in capital lettersvalueOf() is the reverse of name(). You pass a String and it finds the matching enum constant. If you pass "FRIDAY" it returns WeekDay.FRIDAY:
java
// Pass a String, get back the matching enum constant
WeekDay day = WeekDay.valueOf("FRIDAY");
System.out.println(day); // prints FRIDAYThis is useful when you receive a day name from user input or a configuration file as a String and need to convert it to a proper typed enum constant.
Adding Fields and Custom Constructors to an Enum
Plain enums with just names are already useful, but enums can do much more. You can add fields, constructors, and methods to an enum. This lets each constant carry its own extra data.
Think of it this way: the enum still represents a fixed set of constants, but now each constant has its own set of properties attached to it.
Here is an example where each day of the week carries a numeric value and a description:
java
public enum EnumSample {
MONDAY(1, "Start of work week"),
TUESDAY(2, "Second day"),
WEDNESDAY(3, "Middle of week"),
THURSDAY(4, "Fourth day"),
FRIDAY(5, "End of work week"),
SATURDAY(6, "Weekend begins"),
SUNDAY(7, "Rest day"); // semicolon required before field/method declarations
// Fields that belong to each constant
private final int val; // numeric value for this day
private final String comment; // description for this day
// Constructor - always private in an enum, even if you write it as default
// Java will make it private in the bytecode regardless
EnumSample(int val, String comment) {
this.val = val;
this.comment = comment;
}
// Getter for val
public int getVal() {
return val;
}
// Getter for comment
public String getComment() {
return comment;
}
}Notice how each constant is followed by its values in parentheses: MONDAY(1, "Start of work week"). These values are passed to the constructor. Monday gets val 1 and the comment "Start of work week", Tuesday gets val 2, and so on.
Remember one important rule: whatever fields and methods you define inside an enum belong to each constant individually. Every single constant gets its own copy of those fields. MONDAY has its own val and comment. TUESDAY has its own val and comment. They are separate.
You access these values like this:
java
EnumSample day = EnumSample.MONDAY;
System.out.println(day.getVal()); // prints 1
System.out.println(day.getComment()); // prints "Start of work week"A method that should belong to the enum as a whole, not to each constant, is declared static. For example, finding the constant for a value:
java
public static EnumSample getEnumFromValue(int value) {
for (EnumSample sample : EnumSample.values()) {
if (sample.val == value) {
return sample;
}
}
return null;
}
EnumSample d = EnumSample.getEnumFromValue(7);
System.out.println(d.getComment()); // prints "Rest day"Adding Regular Methods to an Enum
You can also add regular instance methods to an enum. Any method you define applies to every constant. Here is a method that prints a message:
java
public enum EnumSample {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
// A method available on every constant
public void dummyMethod() {
System.out.println("Default dummy method");
}
}Every constant gets this method:
java
EnumSample.TUESDAY.dummyMethod(); // prints "Default dummy method"
EnumSample.THURSDAY.dummyMethod(); // also prints "Default dummy method"But what if one specific constant wants different behavior? You can override the method for just that constant by providing an inline implementation right after the constant name:
java
public enum EnumSample {
// MONDAY overrides dummyMethod with its own version
MONDAY {
@Override
public void dummyMethod() {
System.out.println("Monday specific behavior");
}
},
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
// Default implementation used by all constants that do not override
public void dummyMethod() {
System.out.println("Default dummy method");
}
}Now MONDAY has its own version of dummyMethod, while every other constant uses the default.
Enum with Abstract Methods
You can take this one step further and declare the method as abstract. When a method is abstract, there is no default implementation. Every single constant must provide its own version. This is a powerful pattern when each constant truly needs completely different behavior:
java
public enum EnumSample {
// Every constant must implement the abstract method
MONDAY {
@Override
public void dummyMethod() {
System.out.println("Monday implementation");
}
},
TUESDAY {
@Override
public void dummyMethod() {
System.out.println("Tuesday implementation");
}
},
WEDNESDAY {
@Override
public void dummyMethod() {
System.out.println("Wednesday implementation");
}
},
// ... and so on for every constant
THURSDAY {
@Override
public void dummyMethod() {
System.out.println("Thursday implementation");
}
},
FRIDAY {
@Override
public void dummyMethod() {
System.out.println("Friday implementation");
}
},
SATURDAY {
@Override
public void dummyMethod() {
System.out.println("Saturday implementation");
}
},
SUNDAY {
@Override
public void dummyMethod() {
System.out.println("Sunday implementation");
}
}; // semicolon required before the abstract method declaration
// Abstract method - every constant MUST provide an implementation
public abstract void dummyMethod();
}If you declare a method abstract and forget to implement it in even one constant, the compiler will refuse to compile. This guarantees that every constant is complete.
Enum Implementing an Interface
An enum cannot extend a class, but it can implement an interface. This is useful when you want all constants to fulfill a contract defined by an interface. Interfaces are perfect for behavior that is truly common to all constants because you write the implementation once, not separately for each constant.
Here is an example where an interface requires a method to convert the constant's name to lowercase:
java
// Interface that requires a toLowercase method
public interface Displayable {
String toLowercase();
}java
// Enum implementing the interface - one implementation for all constants
public enum EnumSample implements Displayable {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
// One implementation shared by every constant
@Override
public String toLowercase() {
// this.name() returns the constant name like "MONDAY"
// .toLowerCase() converts it to "monday"
return this.name().toLowerCase();
}
}Now every constant has the toLowercase() method:
java
System.out.println(EnumSample.MONDAY.toLowercase()); // prints "monday"
System.out.println(EnumSample.FRIDAY.toLowercase()); // prints "friday"Because this behavior is truly the same for every constant, putting it in an interface implementation is cleaner than making it abstract and writing the same code seven times. When you find yourself repeating identical logic in every constant, that is a signal to use an interface instead of an abstract method.
The Complete Type Safety Advantage
Now you can clearly see why enum defeats the old integer constant approach. Look at the comparison:
java
// Old approach: accepts any integer, no safety
public boolean isWeekendOldWay(int day) {
if (day == 5 || day == 6) { // magic numbers, hard to read
return true;
}
return false;
}
// New approach with enum: only WeekDay constants accepted
public boolean isWeekend(WeekDay day) {
if (day == WeekDay.SATURDAY || day == WeekDay.SUNDAY) { // self documenting
return true;
}
return false;
}The old version accepts any int, such as isWeekendOldWay(100), and quietly returns an answer. When you try to call isWeekend(100), the compiler immediately gives you an error. The enum version is also more readable: isWeekend(WeekDay.SUNDAY) needs no lookup table.
Part Three: Final
The final Class: Closing the Door on Inheritance
In Java, any class can normally be extended by another class using the extends keyword. Inheritance is a powerful feature. But sometimes you explicitly do not want a class to be extended. You want to lock it down completely. That is what the final keyword does when applied to a class.
When you declare a class as final, no other class can extend it:
java
// This class cannot be extended - it is locked
public final class TestClass {
public void doSomething() {
System.out.println("Doing something");
}
}If someone tries to extend a final class, the compiler immediately refuses:
java
// This will not compile - TestClass is final
public class AnotherClass extends TestClass {
// COMPILE ERROR: Cannot inherit from final TestClass
}The error message is clear and immediate. You find out at compile time, not at runtime.
The purpose of a final class is exactly this restriction: use it when a class must not be inherited from.
Check yourself
- List the properties that make a class a POJO. Where are POJOs typically used?
- Why can an enum not extend a class? Can it implement interfaces?
- Why can you never write
new WeekDay()? - What do
values(),ordinal(),valueOf()andname()return? - How do you give each enum constant its own values?
- How can one constant change the behavior of a method? What happens with an abstract method in an enum?
- Why is an enum better than a set of
static final intconstants? - What does
finalon a class do?
Answers
- Public class, public no-argument constructor, variables with getters and setters, no framework annotations, and no
extends/implements. Typical uses: mapping an incoming request into the application's own object, and representing database rows (entities). - Every enum already extends
java.lang.Enum, and Java allows only one parent class. Yes, it can implement any number of interfaces. - The enum's constructor is always private, so the only instances are the declared constants.
values(): an array of all constants in declaration order.ordinal(): the constant's position starting at 0.valueOf("FRIDAY"): the constant with that exact (case-sensitive) name.name(): the constant's name as a String.- Declare fields and a constructor, then pass values after each constant:
MONDAY(1, "Start of work week"). - Override the method in a body right after the constant:
MONDAY { ... }. If the method is abstract, every constant must provide its own body. - Readability (
WeekDay.SUNDAYinstead of6) and control: aWeekDayparameter accepts only the declared constants, so invalid values like100do not compile. - The class cannot be extended; trying causes a compile error.