The following is a set of suggestions for best practices in Java coding. It is simply my personal advice based on my experience and from collaborating with others on a company-wide best practices document.
When working with multiple developers, you should have a standard set of code conventions: i.e. indentation, spacing, etc. A good choice is Sun Microsystems' Code Conventions. As a brief summary: class names begin with a capital, while method and package names do not. The basic unit of indentation is four spaces.
In organizing code, you should use many short methods and small classes, rather than a few large classes and long methods. This makes stack traces easier to use and isolates dependencies between pieces of code.
Exceptions in Java are vital to handling problems. Write methods assuming that all of the arguments are properly formed. For methods internal to a product, you should only catch exceptions if you know the source of the problem and have decided how it should be handled. If a problem is fatal, log it and throw an uncaught exception rather than calling System.exit().
If the code is an API used by others, you should catch low-level exceptions such as the ubiquitous NullPointerException and throw a more descriptive exception instead. This can be either a custom exception class or a more specific exception like IllegalArgumentException with an explanatory message. For the same reason, you should prefer to throw a descriptive exception rather than returning null.
System resources are File IO streams, sockets, and database connections -- basically, anything that involves more than just local memory allocation. You should carefully protect system resources from exceptions that happen before they are closed. When an object containing a system resource is created, the line after the object is created should always be the start of a try block.
For example:
Socket socket = new Socket(ip, port); try { ... // do something with socket. } finally { socket.close(); }
Don't explicitly manage transactions by using transaction begin and commit/rollback. That should be left to the container in almost every case. This includes code in stored procedures. You should also be careful with collections such as ArrayList, HashMap, and Vector that have indefinite life spans. While Java does not have "memory leaks" per se, it can have infinitely growing collections, which are essentially the same thing.
JNI and native code present a unique risk to Java application servers. Instead of a stack trace for a single thread and a failed request in the event of a coding bug, the whole application server process, with all its pending transactions, can be lost with no audit trail. If native code is required, it should be removed to an out-of-process CGI, servlet or RMI server with a stateless interface.
Do not create static data that can be modified and accessed from multiple threads without protecting it with "synchronized" accessor methods. Do not use "synchronized" at all in EJB's or any classes that will be called from EJBs.
Do not create threads in EJB or servlet code that is called on a per-request basis. This does not include code that is called only once at system startup or under controlled circumstances. Let the container's thread pool do its job of keeping the number of threads finite and tunable.
XML provides a universal, transparent, extensible, and interoperable data exchange format. The technology is embraced by industry leaders with significant investment. Any project using XML should use either document type definitions, or DTDs, or XML schemas to describe the data used.
There are a somewhat bewildering array of choices for XML handling in Java. These boil down to two main types of handling: event-based parsing, and Document Object Model (DOM) parsing. For DOM parsing, I highly suggest the JDOM project as a standard.
There are also more complex technologies built upon XML. Simple Object Access Protocol (SOAP) is a much-touted "cutting edge" solution, but it should be approached with more caution than mature technologies. SOAP vendor compatibility can be an issue, especially in the area of complex data types, advanced header features, stateful extensions, fault handling, and language/technology bindings. Until this area matures, using simple parameters and literal XML encoding can, in some cases, circumvent issues.
A plan for versioning Web services should be considered before the first release of a service. Clients should not expect services to have an infinite support life and should fail gracefully, or be able to upgrade themselves in the event of service version end-of-life. Since Web services by nature are often used more in inter-department, B2B, or B2C integration projects than within a single project, it should be acknowledged that version upgrade paths and timing will be more difficult and expensive to coordinate. Creating the interface so that it plans to evolve by method and field addition, rather than modification or deletion, is helpful.