There are different approaches to upgrade from older AEM versions to newer ones.
In place upgrade - basically stopping your instance, replacing the quickstart.jar with the newer version and starting it again.
Upgrade to a new environment - setting up a new environment with the target version and migrating your project (content, code, configurations, etc.) to that new environment.
From my experience the most reliable and clean way is a combination of these two approaches:
Setup a new, clean environment with the target version.
Perform an in-place upgrade on the old environment.
Migrate everything from the old to the new instance (e.g. using the crx2oak tool).
The upgrade process includes rewriting/relocation/transformation of certain elements in the content. This step may get lost if you just migrate your content from an old 6.2 or 6.3 instance to a new 6.5 environment using a content migration tool, such as crx2oak.
Apart from the "pure" instance upgrade/migration, please also consider the following tasks that make sense and/or are necessary for an upgrade:
Perform some clean-ups and grooming on your repository before migration. Get rid of everything that is no longer needed, execute the common maintenance tasks (workflow and version purge) and run a repository compaction.
Upgrade your projects dependencies (primarily the uber-jar).
Refactor your code to ensure it's functioning as expected under the new version and with the new dependencies.
Consider upgrading any 3rd party libraries, integrations and similar to their respective latest versions as well.
Consider some code refactoring work to follow the latest recommendations and best practices, avoid usage of deprecated APIs/features/etc. and make your code base as modern, clean and up-to-date as possible.
Consider automating the provisioning and configuration of your AEM environments.
Please also note: the bigger the upgrade version step, the more work it is. So plan to test the actual migration process, ideally on a production clone to ensure that you are comfortable with it and there are no surprises during the actual migration run.