Load on startup, as the name implies will force a
servlet to load when the app is started. This is useful if the servlet has some heavy weight tasks to perform in order to initialize itself such as reading config files, getting init information from a database etc..
Before servlet spec 2.3 this was also the only way to force initialization code required by the whole app to be run.
There are a few problems with this and context listeners solve them.
First and foremost, this runs against the principal of a servlet being an object that waits for requests and responds to them.
Second, according to the spec, a container can unload a servlet to conserve resources if necessary. It would then reload it again if a request was made for it. If this were to happen to your init servlet, your initialization setup and teardown code could be run at random times during your app's lifecycle.
The context listener's contextInitialized method gets called once, when the app loads, before any servlets are loaded. Likewise the contextDestroyed method gets called once, after all of the servlets have been unloaded.
These methods cleaner and more self evident.
Load-on-startup is still useful but should only be used to fire initialization code for a particular servlet, not the app as a whole.