The final keyword in Java has been around since version one and has become a Marmite feature in the community.

I first began to use the keyword after a few years of developing with Java. Before then it was always something that I was aware of from boring Oracle docs, or outdated Java books gathering dust in the office.

Advocates of final say that it gives an additional layer of security to an application, opponents say it hinders development and is a waste of five character bytes in the code.

A summary of the use-cases are as follows, with my own anecdotal examples added:

Final Variables

The final keyword can be applied at the variable level as a modifier for primitives and objects to enforce immutability. However with nuanced differences; you cannot change a final primitive's value and for a final object you cannot change its object reference value i.e. you cannot change the value of the pointer.

@Service
public class TicketServiceImpl implements TicketService {

    public static final String TICKET_PREFIX = "XPK";

    private final int ticketLimit;

    private final TicketRepository repository;
    

    @Autowired
    public TicketBeeServiceImpl(TicketRepository repository,
                                int ticketLimit) {
        this.repository = repository;
        this.ticketLimit = ticketLimit;
    }
    
    public String buildTicketForUserAndShow(final String userId, final String showId) {
    
        final List<Ticket> userTickets = repository.getTicketsByUserAndShowId(userId, showId);
        
        if (userTickets.size() < ticketLimit) {
            
            final Integer ticketRecordId = repository.createTicket(userId, showId);
            final String newTicketId = TICKET_PREFIX + userId + showId + ticketRecordId;
            
            // do stuff
            return newTicketId;
        }
    
    ...

In this example we have many variables using the final keyword:

  • TICKET_PREFIX which is behaving like a public constant string, which you might need to reference elsewhere in the codebase. This is comparable to defining a constant value in C++. I don't want this value to change across any instances of this class which could cause side affects (tickets with different prefixes "PBA, DCA"). [1]
  • ticketLimit which is an integer I want defined at construction of the ticket service that can be configurable. Since this value will impact business logic in the class, I do not want it to become mutable, which could result in bugs (someone buying more tickets than they are allowed).
  • repository an object which is injected into the class at construction. I use finality here to enforce dependency injection in this class. It prevents anyone in future trying to instantiate another repository and going against the pattern defined.
  • local variables userTickets, ticketRecordId and newTicketId which are a variety of types. As Java developers, we know that strings are inherently immutable, however they can still be reassigned to another object reference if we do not make them final. This would be bad in this example as it could create incorrect ticket strings, meaning a user can't get into a event!

Note: the constructor and function have their arguments marked with final too, this adds an additional layer of security from preventing the mutation of an argument in the function block.

public Integer calculate(Integer x, Integer y) {

	y = new Integer("42");
	return x.add(y);
}

In the above code, y has been assigned a new object value, which has directly affected the behaviour of this code, ignoring whatever argument value was originally passed in.

In Java 8 it is worth noting that arguments and local variables are assumed to be "effectively" final. If I removed the final keyword in this code it would not give any compiler warnings. This is because I am not mutating any of the local variables to give undesirable side-effects. [2]

If you are wanting to commit to finals at the variable level, then not applying them to local variables and arguments is just adding another overhead in the programmer's brain when writing or debugging. It would be like cutting holes in a safety net, you have now introduced ways to fall down in the code.

You might be thinking, why go to this trouble especially if you are a Java developer who has made it this far without the need for finals?

Well, take a look over the fence at the whacky world of Javascript development, we notice a major drive in the usage of const and let for variables. This is a good practice and should be mirrored across the stack in Java as well. Especially when you have developers working full-stack, why add an overhead, when they can apply the same pattern everywhere.

Final Methods

Final methods allow developers to restrict overriding of methods when they are inherited from a base class.

public abstract class TaxType {

	private final BigDecimal taxRate;

	public final BigDecimal calculateTax(final BigDecimal subTotal) {
		
		return subTotal.multiply(taxRate)
				.setScale(2, RoundingMode.HALF_UP);

	}
}

In this example we have an abstract concept of a TaxType with one final method defined. This method is locked to multiple big decimals and use a rounding strategy of half up to the second decimal point.

public class UKVAT extends TaxType {

	private final BigDecimal taxRate = BigDecimal.valueOf(20);

	//cannot implement the calculateTax method, has to use base class function
}

As you can appreciate, currency calculations are very important, especially in the eCommerce world. Giving freedom to developers to implement their own tax calculation and rounding strategies could cause issues (missing pennies) which do add up, especially if you talk to the finance folks.

Practically speaking, I have only had to use a final function in very few occasions. One could argue that a different architectural approach and good unit test coverage could negate the reasons a final function would be implemented for.

Therefore, use final functions sparingly and as always document them.

Final Classes

Inheritance is a great thing, unless you are designing and building API's. Then it can become a muddied cluster of classes extending each other. When it comes to changing one base class, you now have changed other areas of the interface spec.

In API design, the solution is to go down the avenue of composition; picking and mixing other classes together to define API requests or responses.

Having the final keyword at the class level, is really useful for this matter.

final class PriceResponse {}
final class AddressResponse {}
final class TicketResponse {}

final class ShoppingCartResponse {

	private PriceResponse price;
	private AddressResponse address;
	private TicketResponse ticket;
}

In this example there are four final classes PriceResponse, AddressResponse, TicketResponse; which lets pretend have lots of properties and useful data.

We then have a ShoppingCartResponse which has to explicitly compose itself of the above classes. It does not extend from one. The antithesis of this would be something like this:

public class ShoppingCartResponse extends PriceResponse { 

	//stuff
}

When a developer adds or changes something to PriceResponse, it will fundamentally affect the ShoppingCartResponse, which might require more dev work or introduce bugs.  

Having the composed response, allows the ShoppingCartResponse to posess but not depend on the other classes; they can be null or instantiated. In my opinion it is also much neater, as the main class respects it member object's encapsulation, delegating when need be and nothing else.

Final thoughts

(no pun intended)

I always like to bring up final with other Java developers, it is always interesting seeing how passionate people can get over a five lettered keyword.

One funny story I heard was from a dev team that decided to remove all traces of the final keyword in their codebase, they totalled in around 100 kilobytes of freed space.

For those averse to having final cluttering their codebase but still want immutability, there is the Immutables library.

IDE's like IntelliJ and code-linters are great at analysing code for bugs, i.e. variables not behaving in an "effectively final" behaviour. Which can be easily displayed to the programmer for fixing.

As a developer I want to have a healthy codebase which can be picked up by other developers with ease. Having the option of using the final keyword to direct how a codebase should evolve in the future (for better or worse), is a nice thing to have.

Naturally this should be discussed with a team as a whole, and should be fully committed to or not, using final inconsistently in the codebase will do more harm than good.