Compile-time errors are the best
Ideally, you want someone else to find your errors. Compiler error checking is your friend because it finds errors you've made immediately and tells you about them.
Some development systems use continuous compilation, which informs you about problems as you type with jagged red lines appearing under your errors. The quality of compile-time error diagnosis is a function of both language and compiler design. The basic Sun javac compiler that many development systems use gives relatively poor error diagnostics. On the other hand, the language design has many helpful features, and more are being added. Java's strong typing (variable types must be declared), required casting are good features, asserts, and exceptions all help produce correct code. Java 5 has further further improved reliability with generics (templates), type-safe enums, and a "foreach" statement. The goal of not allowing illegal programs to compile is impossible, but its interesting to think of how the language design could be improved to help prevent or detect errors at compile time.
Catch runtime errors early if possible
The worst kind of runtime error causes the program to slowly fall apart. Perhaps the greatest leap that Java made over C++ is providing runtime checks for common errors such as array indexes out of bounds and storage management, especially automatic garbage collection. Yet, there are still many possible run-time errors. Java provides another great tool in the assert statement which checks that things that must be true really are. Learn about assertions and use them.
Rigorous testing
While it is true that testing can only show the presence of bugs, not their absence, it essential. Methodologies such as Extreme Programming require that test data be written before the code. The JUnit testing facility makes writing and running tests much easier. With fast machines it is possible to run regression tests frequently.
Fail early, fail often
You will write bugs and you want them caught as early as possible. Use types to help catch errors at compile time if possible; use assertions to stop execution if something is wrong; use rigorous testing to find errors.


