What are the best practices for handling exceptions in Java? How can developers create reliable and maintainable code by effectively using try-catch blocks, custom exceptions, and exception propagation strategies? Please provide examples of common scenarios and how to handle them using Java's exception-handling mechanisms.