The shape of it
Two branches are protected and nobody commits to them directly: main, which only holds what has shipped, and develop, where finished features land before they become a release candidate. Everything else is disposable. feature/* carries work in progress. release/* carries a numbered candidate through its final checks. Both get deleted when their job is done. The permanent record lives in merge history and the tag, not in a long-lived branch name.
This is the 2010 Vincent Driessen gitflow model, mostly unchanged, and still common on release-managed estates. Whether it is the right call depends on fit. Gitflow earns its ceremony when releases are coordinated across teams and gated outside git: a change advisory board, a fixed deployment window, a regulatory sign-off. Trunk-based development with feature flags is faster when the team can deploy the moment code merges. The tell is in the workflow itself: a release branch sits built and verified in pre-prod, waiting for a change window, because the team cannot ship on merge. Teams that can, skip this step.
The version string is the environment gate
The rule that pre-prod and production can only deploy from a release/* branch running a non-SNAPSHOT build is Maven's own versioning contract doing the enforcement, not a policy bolted on top of git. A -SNAPSHOT coordinate is deliberately mutable, it resolves to whatever was last deployed under that identifier, which is exactly what you want while a feature is still moving. Strip the suffix and the coordinate becomes permanent, that exact jar or image, forever. You cannot accidentally promote a moving target into production, because a moving target cannot resolve to a pinned build in the first place. The safety rail is the artifact system itself, not a separate policy layer someone has to remember to check.
Most teams treat SNAPSHOT as a versioning convention. Used this way it is a deployment gate, because Maven will not give you a deterministic resolve on a SNAPSHOT coordinate. The version string does the enforcing.
Four pom.xml files, one human keeping them in sync
Here is the part of the model that is still running on discipline instead of tooling. Four separate pom.xml files need the same version string, and the source document says it plainly: get it wrong and you will get a build error. That sentence, in bold, on a wiki page, is a documented failure mode being handled by asking someone to read carefully at the exact moment they are least likely to, mid-release, under a deadline.
If these four modules are a real Maven reactor, this should never be a four-file manual edit. A parent POM holds the version once, child modules inherit it, and the actual bump is a single command run as a CI step the moment a feature/* or release/* branch is created:
That turns a step in a wiki that four different engineers have to execute identically into a step nobody has to remember at all. If the four poms are not actually a reactor, if they are independent artifacts that happen to need coincidentally matching versions, that is a structural smell worth naming on its own, not a versioning problem to work around.
Starting and finishing a feature
One default worth keeping: a feature/* branch cannot reach any real environment unless someone deliberately sets DEPLOY_FEATURE_BRANCHES=true for that run. In-progress work is invisible everywhere by default. Visibility is opt-in, not something you have to remember to prevent.
What this looks like with three people pushing at once
Three engineers, three feature branches, all cut from the same develop, all landing back on it at different times, then one release branch that carries the lot through to production. Press play.
R, M and W branch off develop independently and commit at their own pace. Each merges back on its own schedule once it clears UAT. Once develop is stable, release/2.4.0 is cut, SNAPSHOT is stripped from all four poms, and the release branch is built through pre-prod before anything asks a change board for a window. The tag gets cut, the merge goes to both main and develop, and the release branch disappears. The prod chip is the only thing that lights up during the actual change window.
The release branch and the pre-prod head start
Building the release branch all the way to pre-prod before the change window opens is the best operational call in this model, and it is easy to miss why. By the time the CHG record exists, the image is already in the production Docker repository. The window itself is a promotion of an already-built, already-verified artifact, not a build-and-deploy race against a change board clock. If the CHG slips a day, nothing is lost. The artifact waits.
Compare that to building inside the window. Compile failures, flaky integration tests, and slow dependency pulls now happen inside the one hour you got approved for. Front-load the expensive, failure-prone work into a low-stakes rehearsal and leave the cheap, fast promotion for the window. That idea holds even if you dislike gitflow as a branching model.
Where the human is still the control plane
The part that has not caught up is the pipeline run screen. Getting a release to pre-prod, then to production, means setting eight to ten independent booleans by hand, in the right combination, twice: once for pre-prod with production stages held false, then again for cutover with a different set flipped. That is a checklist wearing pipeline clothes, and checklists fail when people are tired, rushed, or new.
Get one boolean wrong and the failure modes are not symmetric. Skip a test stage silently and you find out later, expensively. Flip a production deploy flag on the wrong run and an unverified build ships. The version-string gate only catches the SNAPSHOT case. It does nothing if someone runs the correct release branch with the wrong stages enabled.
Two fixes, neither exotic. Collapse the boolean matrix into named presets such as build-and-verify-to-preprod and promote-to-prod, so a human picks one action instead of ten switches. And put the hard rule, release/* branch pattern plus non-SNAPSHOT version, into a pipeline guard that fails fast on its own. The version gate and the pipeline guard should both hold independently. Right now only one of them is enforced by anything other than a careful reader.