The Model-View-Controller architectural pattern is a classic trait of Java applications. For example, Swing is a well-known implementation of this pattern in the context of desktop applications.
MVC is part of the oldies but goodies group of patterns: since the SmallTalk era everyone keeps talking about it, and reimplements it in different variants, like the boundary-control-entity trichotomy.
For the web
MVC is also successful on the web, with some changes to the original proposed architecture, which deals with non-distributed applications: some problems like how the view can listen to changes in the model arise.
Though, the adaptation of MVC to web-based application is good and versatile: the Controllers listen to HTTP requests, play with objects from the Model and pass control to the View for producing an HTML response. Different views supports more than one format of the response, while different controllers should be called dependending on the request type. But this is advanced stuff: let's simplify the approach as much as possible to get started with this pattern.
Sometimes we do not want to use a framework like Struts, as we ought to create only a simple application; or maybe a web interface is secondary to our application, which mostly performs asynchronous work and presents its result via web.
Still, MVC promotes separation of concerns and we may want to employ this pattern, with the only aid of a servlet container like Tomcat. The goal of this article is to present an implementation of MVC within a servlet container, which produces a thin client web application. MVC's intent is pretty alive even in this implementation, and intent is the most important part of a pattern.
A servlet container already performs a lot of work. Just think of mapping urls to servlets: web.xml takes care of this declaratively, but it's something we couldn't take for granted in other languages like PHP (where every framework implements its own url-controller mapping metadata.)
The approach I'll present is what we do in a basic web technologies course, and it's more or less what Java MVC frameworks for the web do under the hood.
Model (and infrastructure)
Let's start with the Model component of a web application. Your model it's only yours, so it shouldn't rely on base classes to extend like in Swing's implementation of MVC, or first versions of EJB. The Domain Model pattern is by far the best way to craft a Model for MVC applications,and Java platform has all the different tools that let you code a Model without intrusion (such as Hibernate).
The infrastructure should be bridged to the model with different adapters, whose interfaces reside in the model. If you're doing this right, ideally with your model and infrastructure adapters (for example for persistence) in conjuction you should quickly be able to perform any operation in your implemented user stories/requirements, by manipulating these objects which correctly encapsulates all the business logic of the application.
What goes here: persistence and queries, logic you want to test.
In our minimal approach to MVC on the web, servlets are [action] controllers. They are not testable in isolation (we are not able to inject collaborators in them), so keep this layer thin, in order for the relevant business logic to be kept in the model. The single entry point of the application is the servlet container, which chooses the servlet (controller) to run by configuration.
The job of servlets (and in general of controllers) is managing parameters and wiring model objects to the view variables. Everything else should be isolated in a testable external component (like validation of data). Servlets basically set up request or session attributes with data extracted from the model (usually beans), and include or forward to JSP files which will complete the presentational work. They can also convert the format of data from domain objects to something simpler, but usually JSP are fine by working against a bean interface.
What goes here: wiring of objects together, orthogonal concerns like authentication and authorization.
Again, we have to craft a minimal solution. JSP are our views: they encapsulate the creation of the final response, starting from a set of entities and value objects prepared from the controller (kept in the request scope). Thus JSP are rarely called directly, only for nearly-static pages.
The JSP work consists in iterating over beans or other objects, and mostly printing values or including other JSPs. Move all the non-presentational logic out of the views, and keep it either in the controllers (for application-related logic) or in the model (when it is correlated to the domain). JSP are not meant for containing complex logic but for being susceptible to the access from designers and people who do not speak Java but only HTML and CSS.
What goes here: no queries, no call to external services. Write HTML or your preferred format, period.
Problems will rise
Generating HTML is tedious even in JSP. When it comes the time, you can explore tag libraries (but they are a bit complex to use in respect to plain old helper methods) or reusable helper classes which generate HTML from an intermediate representation (Collections or List of Objects) which can be developed and tested independently. Presentation logic is not as simple as it seems: we keep it separate from business one and attack it with different tools.
Moreover, multiple formats require a front controller which switches the view to include under some convention, to produce HTML, XML, JSON, or a response in some other format. Here a framework like Struts goes a long way and you shouldn't reinvent the wheel if your app does need this features (it's not so simple anymore).
Finally, the Two Step-View pattern will emerge sooner or later: how can we create a common layout for all pages or for groups of them? You can define an header() and footer() in a base servlet method and call it in a includeJsp() one, so that your servlets will implement this pattern without knowing it. It would not provide different layouts to choose between, but extracting an header and a footer is the first refactoring of large-scale presentation logic.