Cover of Effective Java

Book Highlights

Effective Java

by Joshua Bloch

What it's about

This guide provides practical, battle-tested advice for writing robust and maintainable Java code. It moves beyond basic syntax to explain the idiomatic patterns that define professional-grade software development in the Java ecosystem.

Key ideas

  • Static factory methods: Use these instead of constructors to provide descriptive names and control object creation.
  • Serialization risks: Avoid Java serialization entirely due to its massive security attack surface and prefer formats like JSON or Protobuf.
  • API design: Design interfaces that return empty collections rather than null to prevent common runtime errors.
  • Concurrency: Rely on high-level concurrency utilities instead of low-level wait and notify mechanisms to ensure correctness.
  • Generics over arrays: Favor generics for type safety because they catch errors at compile time, unlike covariant arrays.

You'll love this book if...

  • You enjoy writing code that is clean, readable, and avoids common traps.
  • You're looking for expert-level guidance on how to use Java features correctly rather than just knowing they exist.

Best for

Software engineers who want to transition from writing code that works to writing code that is professional and maintainable.

Books with the same vibe

  • Clean Code by Robert C. Martin
  • The Pragmatic Programmer by Andrew Hunt and David Thomas
  • Java Concurrency in Practice by Brian Goetz

28 popular highlights from this book

Key Insights & Memorable Quotes

The most popular highlights from Effective Java, saved by readers on Screvi.

Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.
One advantage of static factory methods is that, unlike constructors, they have names.
Collection or an appropriate subtype is generally the best return type for a public, sequence- returning method.
Writing concurrent programs in Java keeps getting easier, but writing concurrent programs that are correct and fast is as difficult as it ever was.
If you export a nontrivial interface, you should strongly consider providing a skeletal implementation to go with it. To the extent possible, you should provide the skeletal implementation via default methods on the interface so that all implementors of the interface can make use of it.
In other words, if you accept the default serialized form, the class’s private and package-private instance fields become part of its exported API, and the practice of minimizing access to fields loses its effectiveness as a tool for information hiding.
streams do not make iteration obsolete
In summary, serialization is dangerous and should be avoided. If you are designing a system from scratch, use a cross-platform structured-data representation such as JSON or protobuf instead. Do not deserialize untrusted data. If you must do so, use object deserialization filtering, but be aware that it is not guaranteed to thwart all attacks. Avoid writing serializable classes. If you must do so, exercise great caution.
Implementing a constant interface causes this implementation detail to leak into the class’s exported API. It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them. Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface.
There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you’re willing to forgo the benefits of object-oriented abstraction.
Builder pattern is more verbose than the telescoping constructor pattern, so it should be used only if there are enough parameters, say, four or more.
Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.
A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they're invoked.
A forEach operation that does anything more than present the result of the computation performed by a stream is a “bad smell in code,” as is a lambda that mutates state.
A fundamental problem with serialization is that its attack surface is too big to protect, and constantly growing: Object graphs are deserialized by invoking the readObject method on an ObjectInputStream. This method is essentially a magic constructor that can be made to instantiate objects of almost any type on the class path, so long as the type implements the Serializable interface. In the process of deserializing a byte stream, this method can execute code from any of these types, so the code for all of these types is part of the attack surface.
You can put any element into a collection with a raw type, easily corrupting the collection’s type invariant (as demonstrated by the unsafeAdd method on page 119); you can’t put any ele- ment (other than null) into a Collection<?>.
When used to best advantage, exceptions can improve a program’s readability, reliability, and maintainability. When used improperly, they can have the opposite effect.
To summarize, just because you can overload methods doesn’t mean you should.
In fact, two-thirds of the uses of the close method in the Java libraries were wrong in 2007.
The behavior of this program is counterintuitive because selection among overloaded methods is static, while selection among overridden methods is dynamic.
In summary, never return null in place of an empty array or collection. It makes your API more difficult to use and more prone to error, and it has no performance advantages.
arrays are covariant. This scary-sounding word means simply that if Sub is a subtype of Super, then the array type Sub[] is a subtype of the array type Super[]. Generics, by contrast, are invariant: for any two distinct types Type1 and Type2, List is neither a subtype nor a supertype of List. You might think this means that generics are deficient, but arguably it is arrays that are deficient.
and then tried to use it outside the classroom, you know that there are three things you must master: how the language is structured (grammar), how to name things you want to talk about (vocabulary), and the customary and effective ways
In other words, it is about 50 times slower to create and destroy objects with finalizers.
It is too early to say whether modules will achieve widespread use outside of the JDK itself. In the meantime, it seems best to avoid them unless you have a compelling need.
Given all the problems associated with Cloneable, new interfaces should not extend it, and new extendable classes should not implement it.
This leaves a big question unanswered. Is it ever appropriate to store an optional in an instance field? Often it’s a “bad smell”: it suggests that perhaps you should have a subclass containing the optional fields. But sometimes it may be justified.
premature optimization is the root of all evil. —Donald E. Knuth [

Find Another Book

Search by title or author to explore highlights from other books.

Try it with your highlights

Create your account, add your highlights and see how Screvi can change the way you read.

Get Started for Free(No credit card required)