Source Code Management is not an optional infrastructure. It is the foundation that lets a Java team collaborate, reproduce releases, recover old versions, and keep the software life cycle controlled.
Git is the common default today because it gives every developer a local repository, supports branching without needing constant access to a central server, identifies versions with cryptographic hashes, and works through familiar protocols such as HTTP, SSH, and FTP. Those capabilities are useful on their own, but the real value appears when the team agrees on a workflow.
A branching strategy defines how code moves from local development to production. Without one, every release becomes a negotiation. With one, the team knows where active work happens, where release candidates are stabilized, and how urgent production fixes are handled.
The Problem
Many teams use Git commands, but still do not have a source control strategy.
A developer creates a branch, another developer works directly on the main branch, release fixes go somewhere else, and nobody is completely sure which commit went to production.
Uncontrolled flow:
local change
|
v
unknown branch
|
v
manual merge
|
v
production artifact with unclear source
This is dangerous because source code must be traceable. If production fails, the team should be able to answer:
- Which exact code version is running?
- Which commit introduced the behavior?
- Which branch contains the fix?
- Which version can be rebuilt?
- Which release contains the production bug?
Git gives the tools. The workflow gives the discipline.
Git Basics That Matter for Architecture
Git does much more than save files. For release architecture, a few commands are especially important.
git init payment-service
git clone https://example.com/payment-service.git
git add src/main/java
git commit -m "Add payment validation"
git branch feature-payment-validation
git tag 1.2.0
git push origin main
The important concepts behind those commands are:
- A repository stores the project history.
- A commit freezes a set of changes.
- A branch isolates a line of development.
- A tag marks an important point, usually a release.
- A remote repository lets the team share code.
Tags are especially important for production. A release should be identifiable later. If version 1.3.0 is running in production, the team should be able to find the matching tag and rebuild the artifact.
Git Flow
Git Flow is a structured branching model. It uses several long-lived and short-lived branches to separate stable code, work in progress, release stabilization, feature development, and production fixes.
The core branches are usually:
Main:
Stable production-ready code
Dev:
Work in progress for the next version
Feature:
Isolated work for one feature
Release:
Stabilization branch for an upcoming production release
Hotfix:
Urgent branch created from production code
A feature starts from the Dev branch and returns to Dev when complete.
Dev branch
|
v
Feature branch
|
v
Feature completed
|
v
Merge back into Dev
When enough features are ready, a Release branch is created from Dev. The purpose of this branch is stabilization. New features should not be added there. Only release-related changes, bug fixes, documentation, scripts, and final adjustments should be committed.
Dev branch
|
v
Release branch
|
v
Bug fixes and release preparation
|
v
Tag release
|
+--> Main
|
+--> Dev
If production has a serious issue, a Hotfix branch starts from Main. The fix is made there and then merged back into both Main and Dev.
Main branch
|
v
Hotfix branch
|
v
Fix production bug
|
+--> Main
|
+--> Dev
Git Flow is useful when the team wants strict release control. The cost is branch complexity. There are more merges, more rules, and more coordination.
Trunk-Based Development
Trunk-based development is simpler. Developers work on the main branch, or use very short-lived local branches that are merged back quickly, ideally at least daily.
Developer work
|
v
Short-lived branch
|
v
Merge quickly into main
|
v
Automated checks keep main releaseable
This style pairs naturally with CI/CD because the main branch is continuously checked. After each merge, automated processes can build the project, run tests, and stop the pipeline if something breaks.
The benefit is smaller merges and less change management. The risk is that bad code can reach the main branch faster if the team is not disciplined.
Trunk-based development works best when:
- Automated tests run often.
- Broken builds are fixed immediately.
- Developers keep changes small.
- The team treats the main branch as releasable.
- Releases are tagged for traceability.
It requires experience and care, but it avoids the branch overhead of Git Flow.
Choosing Between Git Flow and Trunk-Based Development
The best workflow depends on how the team releases.
| Situation | Better fit |
|---|---|
| Formal release stabilization is needed | Git Flow |
| Hotfixes are common and must be isolated | Git Flow |
| CI/CD and DevOps practices are central | Trunk-based development |
| Teams release frequently | Trunk-based development |
| Developers can keep changes small | Trunk-based development |
| The organization requires strict branch gates | Git Flow |
Git Flow is not wrong, but it can feel heavy for modern CI/CD workflows. Trunk-based development is lighter, but it depends heavily on automated testing and team discipline.
Semantic Versioning
Branching controls how code moves. Versioning controls how releases are named.
Semantic versioning uses three numbers:
MAJOR.MINOR.PATCH
Example:
1.2.3
Each number has a meaning.
MAJOR:
Breaking changes, re-architecture, major updates, API changes
MINOR:
New backward-compatible functionality
PATCH:
Bug fixes without breaking changes
If version 1.2.3 introduces breaking API changes, the next version should be 2.0.0.
If version 1.2.3 adds backward-compatible functionality, the next version should be 1.3.0.
If version 1.2.3 fixes bugs only, the next version should be 1.2.4.
Version numbers can grow naturally. Moving from 1.9.3 to a new minor version means 1.10.0, not 2.0.0.
Labels can add release meaning:
0.x.y:
Early draft or prototype
ALPHA:
Preliminary version
RC:
Release candidate
RELEASE:
Production release
The practical rule is that version numbers should help users and maintainers understand the expected impact of an upgrade.
Practical Workflow
A practical source control workflow for a Java team can look like this:
- Use Git for every project, including small projects.
- Decide whether the team uses Git Flow or trunk-based development.
- Do not let developers work directly on stable production branches unless that is part of the chosen strategy.
- Keep feature branches small.
- Use tags for every release.
- Use semantic versioning for artifacts and releases.
- Treat breaking API changes as major version changes.
- Keep patch releases limited to fixes.
- Connect branch rules to automated tests.
- Make production versions reproducible from tags.
Common Mistakes
The first mistake is using branches without a policy. A branch name alone does not guarantee traceability.
The second mistake is allowing release branches to accumulate new features. A Release branch should stabilize a version, not change its scope.
The third mistake is forgetting to merge hotfixes back into development. If the fix exists only in production, the next release may reintroduce the bug.
The fourth mistake is misusing semantic versioning. A breaking API change should not be hidden inside a minor or patch version.
The fifth mistake is adopting trunk-based development without enough automated testing. Fast merging requires fast feedback.
Checklist
- Every project is stored in Git.
- The team has one documented branching model.
- Production releases are tagged.
- Release tags can be rebuilt.
- Hotfix changes flow back into active development.
- Feature branches are short-lived where possible.
- Semantic versioning is applied consistently.
- API-breaking changes increment the major version.
- CI checks run on the branch that can be released.
- The current production version can be identified quickly.
Conclusion
Good Source Code Management turns Git from a file backup tool into a release discipline.
Use Git Flow when release stabilization and formal branch boundaries matter. Use trunk-based development when CI/CD speed and frequent releases matter more. In both cases, tag releases and apply semantic versioning so every production artifact can be traced, understood, and rebuilt.