Don’t create constants
Aren’t magic numbers (or strings, etc.) a bad practice? Creating constants can be a symptom of some underlying anti-patterns; they treat the symptom, not the cause.
“But I can use them in the tests!”
This is the worst reason to create constants. Tests should know nothing about the implementation other than its public APIs.
“I need to read its value in the test”
Initially, a certain constant was local/private but you promoted it to global for the sake of testing. This was a bad idea because now the tests are coupled with the implementation. Tests should be seen as clients of the implementation (i.e. outsiders) so should only know about its public interface.
⚠️ Changing the visibility of implementation classes, methods, constants, etc. for the sake of testing is an anti-pattern.
🚨
assertEquals("T1000", json.at(JsonConstants.MODEL_PROP).textValue())
✅
assertEquals("T1000", json.at("model").textValue())On the other hand, when a constant is shared between implementation and tests, then you could change its value to a wrong one and no test would fail. The right thing to do is to hardcode the literal in the tests, which goes in line with the Evident Data test pattern:
Evident Data seems to be an exception to the rule that you don’t want magic numbers in your code. Test-Driven Development
In short, tests should provide a safety net for errors when altering a constant, rather than relying on the value being shared.
“I need to change its value in the test”
If you change a constant value in runtime, it’s not a constant anymore, right? The correct thing to do is to inject the value into the component that needs it; it makes dependencies evident:
🚨
ComponentUnderTest.MAX_RETRIES = 5
componentUnderTest = ComponentUnderTest()✅
componentUnderTest = ComponentUnderTest(maxRetries = 5)“But they’re used in multiple places”
So now you got rid of the promiscuous sharing between implementation and tests, but you still want your constants shared because they’re needed in multiple places.
Because distinct features share the same values
Shared constants are similar to public static utilities as they couple everything together. Changing a constant value can have unpredictable consequences in all the places where it’s used.
[…] Constants.CRLF remains alone in a global scope of visibility, without any semantic usage around it. We simply don't know how this object is used, in what context, and how the changes we may make will affect its users. Elegant Objects
Let’s say you have an error message like “client is inactive” at a few web handlers. Do you need to share it across multiple places? Why? So you DRY? But each web handler has its own identity and it’s supposed to evolve independently!
Not all code duplication is knowledge duplication. The Pragmatic Programmer
Because a certain feature or component is split (but should not)
You have a code smell when a technical (or even business) decision impacts multiple places in the code. Some concepts are atomic, which means that a decision in the code and its implications should be co-located. Items that work closely together should be placed next to each other. By sharing constants, are you trying to hide that smell? For example, a shared constant of a database table name hints about functionality that should be together in the first place.
Sharing constants might be a smell of a lack of code cohesion because they bind separate components together:
📝 If you have constants that are part of a component’s public interface (e.g. a React component that has the states LOADING and ERROR or a server-side filter by field name), you might have a valid case for constants. Still, I’d consider creating an enum, if possible.
Never increase, beyond what is necessary, the number of entities required to explain anything. (William of Ockham)
“But I can centralize all the constants in one place”
Do you have a file for storing constants? Are you trying to centralize the configuration? GitHub is riddled with examples of DB table/field names, UI strings, JSON field names, etc. These files are as bad as having a package for exceptions or a file full of enums. It’s like setting your dining table by putting all of the forks in one place, and all of the knives in another. Files with constants are like glue for independent features. These are technical hotspots. Organize code by feature or component rather than technical category.

Files with constants are a smell; you should not need to share them in the first place. Those constants are far from where they’re needed. If they matter to a single component, why can all the codebase access them? Constants like table/field names, JSON keys, and error messages help you to configure very specific places of your code. To sum it up, constants are details of a certain feature or component, so move them to their logical owners thus encapsulating them.
⚠️ A file that is edited due to multiple reasons (e.g. files with constants) likely violates the Single Responsibility Principle.
“But they are standard”
“What about Pi or HTTP status codes?” Well, don’t create those constants yourself. These are real constants since they have a standard agreed meaning. This means they probably belong to the runtime (e.g. Math.PI) or some library that you already have (e.g. HTTP status codes in Apache HttpCore). You may argue we’re creating coupling with a library to which I’d say: this coupling should only exist where you’re using the library already (e.g. an adapter).

“But they improve readability!”
Do they? Consider the example:
const val UPLOADER_TIMEOUT = 30_000
// ...
fileUploader.setTimeout(UPLOADER_TIMEOUT)
🆚
fileUploader.setTimeout(30_000)You can easily see that we’re setting the file upload timeout to 30 seconds. Also, there’s no distance between definition and usage. Why the constant? Check the following example of a value object:
data class Password(val password: String) {
init {
require(password.length() > 7)
}
}Isn’t it clear that the password length must be greater than 7? Doesn’t look magical to me. Hardcoding the number seems very reasonable. Creating a constant will just bring some visual load, split the definition from usage, and worse, the temptation to share it.
Now, let’s see a method that obtains a user profile from an external API:
fun fetchProfile(id: String): Profile {
val httpRequest = HttpRequest.newBuilder()
.uri(URI.create("$apiUrl/profile/$id"))
.GET()
return newHttpClient.send(httpRequest.build(), ofString()).run {
check(statusCode() == HttpStatus.OK)
body().toProfile()
}
}Is it worth putting profile in a constant? I don’t think so. The snippet above has all I need in a small place to understand it. Even if the URL was more complex, I’d expect the enclosing method to be small and provide meaning.
What about repeating JSON keys at an object serialization and deserialization? Wouldn’t you want to store them in constants to avoid issues? Well, tests provide me safety, not shared code. Therefore, I don’t mind repeating the literal for the sake of making the code self-evident.
“But they are magical”
A good reason to create constants is to provide meaning to concepts like mapping low-level codes. Representing an “element not found” with the constant NOT_FOUND rather than -1 prevents repetition and magic. In those cases, make sure they’re local and private in the place that owns them. Share them only if they’re supposed to be part of some public interface.
Another example is when you want to give meaning to an arbitrary value. For example, the number of milliseconds in a week. In this case, you can make use of your runtime for its calculation: MILLISECONDS.convert(7, Days). If that’s not clear enough through the context, consider a private constant or method with the magic number.
Conclusion
Always challenge the need for constants. Constants are implementation details. They create coupling between definition and users harming code cohesion. If you need them, ask first if you’re trying to hide a code smell by using constants to…
🚨 Share values between implementation and tests; 🚨 Share values between features; 🚨 Bridge parts of code that should be together in the first place; 🚨 Centralize control in a single place; 🚨 Improve readability.
There are some good use cases for constants though:
✅ To build semantics on an API’s language (consider an enum instead); ✅ To encode real constants (e.g. Avogadro constant) — but check if a library or the runtime contains them; ✅ To encode magic values like low-level codes or domain-related values (but make them private).
In short, if A and B share a constant, probably they should be the same thing, or, if they aren’t, they should have the values hardcoded instead.
