As a consultant asked to maintain and expand portal applications, one of the problems I run across is "portlet bloat." That is, a single portlet solves all the problems of an application on its own as if it's a full-blown web application. It may be an oversimplification to call a portlet a 'mini web app', but it may help to think of it that way when considering how big it should actually be.
A large amount of code and JSPs are usually an indicator as to whether a portlet is doing too much. More importantly, though, consider whether or not it's taking care of too many types of actions. It's one thing to have a portlet take on many different views if it's a report viewer. It's another thing entirely if, in addition to showing these reports, it also displays menus and allows user selections to effect what's displayed on those reports. You can see this reflected in the above illustration. Instead of lumping everything together, break up those responsibilities into individual portlets that can coordinate their efforts with each other.
As shown above, the responsibilities have been split into separate portlets for report selection, criteria and view. The report selection affects what would show up in the criteria and view, and the criteria affects the results in the view. All portlets would go into a single portlet application deployed together in a single web archive (WAR) file. Not only are concerns separated, but it's actually more useful as an application to the user. They see all options and their results available all within a logical unit. A side benefit to the separation of code is that it definitely makes it a lot easier to write and maintain as you have a smaller amount of code with fewer conditions in each portlet.
When taking on the task of making cooperative portlets, you will want to get very comfortable with handling portlet communication. There are two mechanisms for allowing them to communicate with each other: events and public render parameters.
Events should be the primary portlet communication mechanism since they are more explicit. Using events allows you to have code directly invoked to handle changes (switching to a different JSP, changing criteria, etc.) before the render cycle assembles the final page. In the case above, if a report is selected in the Selection Portlet, you can have it fire a 'ReportTypeChangeEvent' in which the Criteria Portlet and Report View Portlet respond by changing their views. If the report criteria is changed, it can fire a 'ReportCriteriaChangeEvent' in which the Report View can respond to by affecting what is displayed in the report.
Public render parameters can be useful if you have functionality that all your portlets respond to in a similar fashion. They are less useful if there are a larger number of specific actions that need to take place. Public render parameters also need to be explicitly checked for during the render cycle when they would be used. There are also other considerations like portlet caching, which public render parameters can't help with, but I'll save that for a future blog.
I hope this has given you some food for thought on the portlets you've written or are about to write. Splitting up responsibilities among multiple portlets not only makes them more powerful and useful, but will also make them easier to code and maintain.