Hotel Application
One hotel review and booking application, rebuilt five times across a semester. It starts as a JSON parser and ends as a servlet app on MySQL with sessions, bookings and an agent that answers questions about the reviews. The fourth build was HTTP written on a raw socket, before a framework was allowed.
Most course projects end when they are graded. This one kept coming back. The same hotel application arrived five times over a semester, and each version pulled a floor out from under the last: the collection had to survive several threads reading it at once, the search had to answer over HTTP, the HTTP had to come off a socket I opened myself, and the whole thing had to outlive a restart once the data moved into MySQL.
What it does
It holds hotels and their reviews, searches them by hotel and by word, and serves the results to a browser. The last build adds people: registration and login, a session that survives navigation, saved bookings, links out to Expedia with a history of the ones you followed, and a chat window that answers questions about the reviews.
Five builds
1. Parse hotel and review JSON into a model, search it, and cover the query paths with JUnit. 2. Put an agent over the reviews. 3. Make the collection safe for a thread pool to load into. 4. Serve it over HTTP, first from a socket, then from Jetty. 5. Move it onto MySQL and give it accounts.
Writing HTTP before using a server
Build four had two halves. First a server on a raw socket: read the request line and the headers off the stream, work out the method and the path, then write a status line, headers and a body back down it. Second, the same routes on Jetty.
That order changes what a framework looks like. HttpRequest and HttpResponse stop being classes you import and become the things you got wrong first. The blank line between headers and body is not a convention you read about, it is the reason your browser hung.
The agent came before the database
The agent landed in build two, three builds before anything was persisted. It runs through LangChain4j against an OpenAI model, and the tools it can reach are plain Java methods over the review collection: find the reviews for a hotel, find hotels matching a word, and later a weather lookup and a restaurant lookup that go out over the network.
Building it in that order puts the model on the outside. It never holds the data. It asks, and every answer it gives traces back to a method that either returned something or returned nothing. When a hotel has no reviews, the model has nothing to be confident with, which is the behaviour you want and the one you lose if you hand it the whole corpus and hope.
Thread safety, and when it earns its keep
Build three loads reviews on a pool of threads, which breaks the collection the first two builds leaned on. It got a thread-safe wrapper, and progress reporting moved to an observer so the loader could say what it was doing without knowing who was listening.
The lesson was smaller than the pattern: most of the code did not need to know about threads at all. One class did.
The last build
Jetty servlets behind Velocity templates, MySQL underneath, prepared statements for the queries, Hibernate for the entity mapping. Registration and login hold a session. Bookings and the Expedia history are rows keyed to a user. The chat servlet gives the agent from build two a front end, so the piece written in week four is reachable from a browser by week fifteen.
Why there is no link
The repository is coursework, and the course runs again. Publishing it publishes the answers, which is the same reason OSlings stays private while I am marking the exercises inside it.