A few days before writing this post, I shared my thoughts about this topic on X/Twitter, LinkedIn, and Mastodon. The top engagement was on X/Twitter, with LinkedIn second, and Mastodon being minimal (as usual). There was a lot of positive reaction—nearly 900 likes to date—including those expressing their support for my thoughts. Several shared their own struggles with the influences of Inversion of Control Containers and Dependency Injection.
On the flip side, the strongest negative reaction was on Mastodon where someone blamed me and “my generation” for the ruined condition of the planet in abusive metaphors. Sure, that was a bizarre outlier, but there were still a considerable number of toxic comments from others. Reactions like those outbursts are laughable, but the general misinterpretations or misunderstandings about my message call for a richer treatment. That’s what I’ve provided here.
I’ll start with a brief overview of my concerns, reiterating what I wrote, and dismissing what I didn’t write—the misinterpretations that people came up with, upon which they defended their toolset. Following that, I dive into more detail.

Overview
The following bullet points listed are the essence of my message. You have to understand that my thoughts are built from my own experiences. I can’t speak for others, but I’d say that I have a very large set of data points on this subject. I have engaged to a greater or lesser degree with thousands of software developers over many years. I have considerable experience with both Inversion of Control Containers (IoCC) and Dependency Injection (DI). My first book—known among DDD practitioners and enthusiasts as “the red book”—includes example code that uses the Spring Framework. I also have a GitHub repository with contrasting examples from the book, using both IoCC+DI and those not using it. That helps to demonstrate the alternatives.
Martin Fowler traced the origin of Inversion of Control (IoC) back to 1988. The Dependency Inversion Principle, which uses Dependency Injection, was postulated by Robert C. Martin in 1994. Here I am primarily discussing IoCC+DI; that is, I am not primarily discussing IoC outside of the container, but rather the combination of both IoCC and DI used together. When I later discuss DI outside of an IoCC (yes, outside the use of a container, which is quite achievable), I call that out.
With that background, these are the salient points from my original post:
- IoCC and DI have had a very great impact on the software industry
- I’ve experienced the positive and negative effects of IoCC+DI
- The contemporary emphasis when using IoCC+DI is to use constructor injection
- This influence has reached a point that when IoCC+DI developers see a constructor or the use of a constructor, they immediately think and say: “that’s dependency injection”
- This influence has also had the impact that if said developers aren’t using DI for a particular object, they do not create any constructor at all, letting the compiler generate the default, zero-argument constructor
- When the default constructor is used, it requires clients/consumers of the object to initialize the object
- This causes big problems, which I will explain in detail below
- I did not say not to use constructor injection; not even an implicit nod to that
- I did not say to use property setter injection instead; no, nope, negative, by no means, not at all
I’ll now go into more detail, including the history that I am most familiar with.
Social Media Downward Spiral
There is a contingent using social media that almost certainly choose to “misunderstand” what has been written on a given subject. It seems clear because their disagreements begin with passive-aggressive, veiled insults, or outright blatant you-are-stupid abuse, directed at you. The more subtle, passive-aggressive approach is plausibly deniable as it could be seen as some frustration triggered by your “misguided ignorance on the topic.” Their attack begins based on their argumentation that starts out with wrong assumptions. The blatant abuse generally comes from those hiding behind an anonymous, yet active account. From there the “conversation” quickly moves from wrong assumptions and insults to irrelevance and then spirals into assaults on character.
As the target, I have found three ways to approach these situations: (a) ignore, but that doesn’t help if someone is actually genuine but has poor manners, and because some who read their comments will be misled by them; (b) ask for clarification on their points and to whom their veiled insults are directed, which generally leads to no response from them because they will have to overcommit their position and they can’t use your reaction to segue to their attack; (c) openly seek their constructive feedback, knowing that you will soon prove that they have started based on wrong assumptions.
There is the risk that (c) won’t work, because they will take a single objection to what they have said as an excuse to be “offended,” calling out your obvious-to-them character flaws, and then engage their imagined crocodilian death spiral, with their vision of shoving you under a log for later feeding.
It happened at least several times over the past few days on all three social platforms. It’s a clearly recognizable (mis)behavioral pattern. Frankly, it’s a bit entertaining, actually, but perhaps I’ll simply fall back to (a) in the future. You know, if they look like a bully, smell like a bully, and write like a bully, assume they’re a bully. In any case, as I have stated before, when I have the opportunity to help some, the negative interactions are offset by seeing a greater number benefit from my help. The outcome is a net positive.
Some History
My knowledge of IoCC+DI dates back to approximately 2003. Based on my experience, it was the Java community that made a huge investment in IoCC+DI and had the most influence on its use. Other language communities seemed to largely ignore IoCC+DI for a long time, but have introduced it in more recent years (PHP’s Laravel in mid 2011). Java’s lead came about when the Spring Framework was introduced. I don’t recall ever hearing about IoCC+DI before I knew of Spring. There is however, as I recently learned, more history to this.
Niclas Hedhman worked on the team that introduced early IoCC+DI support for Java. First, the Apache Cocoon product introduced a plugin module manager for web applications that supports pipelines (i.e. Pipes and Filters architecture). With Cocoon, new functionality can be plugged into your application and placed in a pipeline that supports clean separation of concerns. Cocoon had a kind of IoCC+DI capability that used a Service Manager to look up missing dependencies.
Niclas and team extracted the Cocoon IoCC+DI functionality and moved it to Apache Avalon. Ultimately Avalon became known as a Type 1 IoCC+DI. Of course, Avalon only became to known as Type 1 when a Type 2 came along. The new Type 2 product was Spring Framework. It was known as Type 2 because it used the JavaBean model to inject dependencies by means of public setters. In my early use, you had to make all of your types that used IoCC+DI follow the JavaBean spec. That’s just the way it worked. Later, constructor-based DI was introduced by Spring. For some reasons that I don’t recall, even with constructor injection available, Spring still emphasized using JavaBeans setter-based injection instead. This mentality hung around for a long time (several years). Even by 2011-2012 when I was working on my red book and examples, property-based injection was still very commonly used. Somewhere in there Spring started advocating constructor-based injection. I don’t know the percentage of use between setter-based injection and constructor-based injection at this point.
Shortly after Spring was released, another Java IoCC+DI container was introduced, known as the PicoContainer. The PicoContainer emphasized the use of constructor-based injection rather than property-based injection. Many considered the PicoContainer to be superior to Spring for this and for other reasons . Even so, as we all know, many wasn’t enough. The PicoContainer hasn’t been updated since 2014 and Spring has dominated the Java IoCC+DI space.
So What?
So, the problem with the problem is that it causes big problems.
Let’s get something straight. Object constructors were not invented for IoCC+DI or DI without IoCC. Object constructors were invented to initialize objects of all kinds upon instantiation (creation), and for all kinds of reasons.
Yet, when many today see a constructor in use, their knee-jerk reaction is to think and say, “that’s dependency injection.” And by this I mean literally. Hence, we’ve got a problem that was born decades ago. The problem is that many/most programmers think that constructors are only useful for DI even though they were never meant for exclusive use by DI. This “infection” is intuitively obvious when you’ve seen and discussed many large system architecture failures—the Big Ball of Mud—that are the bane of every industry that uses software; that being all industries.
On the other hand, the kind of code that I most often demonstrate is in the domain model. The constructors for such domain objects are never used for DI. My domain model types use constructors to initialize their business state, which becomes valid from the beginning of their lifecycle.
As stated above, the mentality that constructors are useful only for DI has generally led to an incorrect understanding. The incorrect understanding is: if your object doesn’t need to have one or more dependencies injected, you don’t need to define any constructor at all, or you need only define a zero-argument constructor. Then, of course, you don’t instantiate an object of that type, you “new it up.” Sigh. Given that “new it up” isn’t even real English and it has one less syllable than “instantiate,” I choose to use the latter.
(Note that not explicitly defining any constructors at all is possible only in languages such as Java and C#, where the compiler generates a zero-argument constructor on behalf of the developer who didn’t define any constructors.)
Due to the general lack of explicit constructor use that initializes an object beyond zeros and nulls—again, when DI is unnecessary for specific objects—there are these ongoing problems:
- Initialization of objects required to place them into a valid state is an abdicated responsibility left to the clients/consumers of such objects
- At least temporarily, such objects not being properly initialized are left in an invalid state
- A basic rule of object orientation is that objects must always be in a valid state, which is not held in such cases
- It is not the responsibility of clients/consumers to initialize objects, yet the responsibility is being foisted on clients/consumers to initialize anyway
- Requiring the clients/consumers to initialize means that the new object’s internals, including its structure and state types, are exposed and leaked outside to clients/consumers, which is about the most fundamental object-oriented programming rule to break
- (The structure is generally flattened to all primitives and strings in order to support setting the “columns” of the glorified “database table row,” which introduced yet another set of problems around reasoning about the implicit meanings and intentions)
- Since state is being explicitly set on the object one primitive/string attribute/property at a time, the object doesn’t have the opportunity to derive one or more states from the provided parameters, because the initialization is not atomic as it is with the use of a constructor
- (For that matter, rich behaviors are also missing for use after construction, and thus transitioning state after initialization has the same limitations and bad effects)
- Since state cannot be derived by the newly instantiated object, derivation must happen in the client/consumer, which is a further leak of knowledge outside the actual responsible party (the object being initialized)
- Because clients/consumers are forced to be responsible for the object’s initialization, there is a risk that the object will not be initialized correctly and will thus be left in an invalid state past intended initialization
- Even if the object is initialized properly early in the software’s lifecycle, it can easily become invalid as changes are made to the object’s design and implementation if the client is not also changed (properties added but not initialized)
Next is a brief example of what I have just outlined. For now think of the Proposal as a description of the work that a Client needs to have done, including the price that they are offering for the work:
var proposal = new Proposal();
proposal.setClientId(clientId); proposal.setDescription(description); proposal.setInstructions(instructions);
proposal.setKeywords(keywords);
proposal.setOfferPrice(offerPrice); proposal.setPricingApprovalPending(true); proposal.setWorkersPending(true); . . .
In case you .NET folks feel left out, I translate the above code for you:
var proposal = new Proposal();
proposal.ClientId = clientId;
proposal.Description = description;
proposal.Instructions = instructions;
proposal.Keywords = keywords;
proposal.OfferPrice = offerPrice;
proposal.PricingApprovalPending = true;
proposal.WorkersPending = true;
. . .
Along with anemic model comes primitive obsession, usually with lots of primitives. The only real objects are of type String/string, neither of which helps when reasoning about the overarching concepts that the business considers essential. Any tacit knowledge understood by the original developer will likely soon be lost, especially when working in a complex domain. There are so many ways that this initialization can go wrong, both currently and in the future. Later the client’s/consumer’s expected scheduling is implemented in the anemic model, but the consumer code overlooks this initialization:
// oops, forgot to initialize these later // when scheduling support was added
proposal.setScheduleStart(scheduleStart); proposal.setScheduleEnd(scheduleEnd);
It’s also unlikely that tests will reveal this problem, or could also go out of sync with the client/consumer code.
What’s the bottom line? If you are employing dependency injection, constructor-based dependency injection is what you should use. Further, when designing an object that does not require DI, just “normal” object initialization, design constructors that take a required number of parameters and use that constructor to initialize the object so that it enters a valid state from the beginning of its lifecycle. Of course, in either case, always create tests that prove the object is valid after construction. After that, learn to design rich behaviors that explicitly show the intention of an object mutation, and that atomically modifies all necessary-to-change state in each rich behavior.
It seems odd to even have to state that constructors should be used for object initialization of all kinds, but that’s where the software industry is.
Implementing Domain-Driven Design Example Code
You’ll see in my GitHub repository that I use constructor-based injection. You’ll see that there are some types that don’t need DI. For example, the Repositories didn’t need to directly inject any dependencies or provide any other initialization. Any DI initializations occur in abstract base classes extended by the Repositories. For the particular beans not requiring DI, I explicitly defined zero-argument constructors to clearly indicate that no special initialization is necessary. Keep in mind that this code is more than 10 years old. I have not changed the bulk of the code since as I’m not in the business of keeping up with changing technologies that I no longer use.
Constructors for All Initialization
To emphasize the use of constructors for the purpose of object initialization other than that of DI, I start with some examples here. Consider the use of the Proposal constructor. As indicated above, Proposal is a type in a domain model, and does not use DI:
var proposal = new Proposal(client, expectations);
The proposal variable now holds a reference to an instance of Proposal that is fully initialized into a valid state. No, this is not DI. The client parameter is not some sort of object that requires mocking in order to test, such as a technology-laden HTTP client that talks over the network. I know for some this can be difficult to grasp because they are thinking so deeply about technology that it’s difficult to pop the stack way up to a business level.
Still, it’s necessary to think in business models when the purpose is modeling business capabilities. Here, client has a non-technical meaning. In case it’s not obvious, looking in a dictionary you’ll find that the word “client” has more than one meaning. As DDD emphasizes, this “client” has a very specific meaning in its particular business context. In this business context it is the client person who is submitting a proposal to be matched with a worker to fulfill their proposed work.
No, expectations is not a proxy to a remote microservice. It’s the expectations that the client is proposing for the work to be done. Both client and expectations are pure business objects, and Value Objects at that. They are lightweight, immutable, and thus, any behavior that they provide is side-effect free. There is nothing super techie about them, but there is a lot of importance associated with the objects from a business perspective.
I roll this into one single statement: the type Client holds the clientId from the above anemic model and Expectations holds all the other properties, but as additional individual Value Objects that cluster the business’s knowledge of client expectations into meaningful whole values.
The client is a reference to the Client in the system of record, which in this case is only an identity. The Client in its system of record (a separate context) is managed there, not where work is being proposed and matched with a worker. All the values held within the expectations value represent a thorough description of the work to be done.
Now, to really blow minds, consider making the above object instantiation fluent. By fluent, I mean that I can write an expression that closely follows the way the business thinks and speaks about this client-worker matching domain. That’s a goal of Domain-Driven Design tactical modeling:
var proposal = Proposal.submitFor(client, expectations);
In order to force the use of this Factory Method (see GoF), I hide the constructor as private. Only the Factory Method submitFor() may use the private constructor. When I say “blow minds,” I am not attempting to insult anyone. The fact is that reactions I have gotten from this simple example have amazed me. People don’t take the time to read my explanation of what’s being done; that the static method is calling the now hidden constructor. They see only the use of a static method and go off the rails in disagreement.
- “Using static methods is bad practice!”
- “You have no idea what you are doing!”
- “You’ve been doing object-oriented programming since 1986, but you still don’t understand it!”
- “You are an idiot trying to tell us that IoCC+DI is a problem and you don’t even know the most basic things about software development!”
Hehehe, wrong. I literally said that the static method is calling the constructor. Are those people trying to tell me that I can test a constructor but I can’t test a Factory Method that calls a constructor?
Even if I were using a static method to call a constructor to inject dependencies (which I am not), I can still test that the result is the same as if I directly used direct constructor-based injection. One person stated, again under the wrong assumption of what a client and expectations are, that I can’t test the static method because client might make a remote service call or do something with a database. First of all, that’s a wildly wrong assumption. Even so, if I’m testing a static method I can’t pass in a mock but when using a constructor I can? Need I say more about the knee-jerk, dogmatic defense of their enamor for their languages and cherished toolset?
Folks need to slow down, read, and think before passing judgement. They need to ask a few questions before making wrong assertions. Otherwise, “it’s a bad look” for them.
Dependency Injection without an IoCC
I will now wrap up my discussion with a look into Hexagonal/Ports and Adapters architecture and the use of DI. To do so, it’s helpful to understand the Dependency Inversion Principle (DIP). I’m going to keep this brief, so please don’t get hung up on my summary of DIP and a simple but useful example.
Using the Layers architecture example from Eric Evans’ blue book, which I also use in Chapter 4 of my red book, these are the common layers from top to bottom:
- User Interface
- Application
- Domain Model
- Infrastructure
All lower-level layers are restricted from depending on higher-level layers. This causes a problem when an interface defined in a higher-level layer needs to be implemented in a lower-level layer such as the infrastructure. Yes, you can implement the interface in the same layer as the interface is defined, but then why use layers at all if concerns can’t be consistently separated? And anyway, do you really want technology in your domain model? Answer: No. No, you do not.
The solution to this conundrum comes from the use of DIP. In essence, DIP says to make higher-level layers depend on interfaces defined in lower-level layers. This is one example of how inversion takes place.
(Note: Of course DIP is more elaborately defined in full. By some definitions it sounds like DIP requires an entirely different place (module, al la package or namespace) to house interfaces that are to be depended on by any layer. For the sake of brevity and to maintain focus on how to deal with typical challenges with the Layers architecture, I won’t describe Ports and Adapters here in those terms.)
Using the principles of Dependency Inversion, the Infrastructure is moved to the top, inverting the direction of interface dependencies that must be implemented there:
- Infrastructure
- User Interface
- Application
- Domain Model: interfaces for Repositories
Given that the Repository tactical pattern always requires a technical implementation for production use, the Infrastructure layer will now depend downward on the Domain Model for Repository interfaces. Doing so adheres to the proper use of Layers. Here’s an example of a Repository that is implemented in the Infrastructure layer, with its interface defined in the Domain Model layer:
// Infrastructure: DataSource package matching.infrastructure.persistence; public interface DataSource { . . . } public class PooledPostgresDataSource implements DataSource { . . . } // Infrastructure: PostgresProposalRepository
package matching.infrastructure.persistence;
import matching.model.proposal.ProposalRepository;
public class PostgresProposalRepository implements ProposalRepository { private DataSource dataSource; public PostgresProposalRepository(DataSource dataSource) { this.dataSource = dataSource;
}
. . .
}
// Domain Model
package matching.model.proposal;
public interface ProposalRepository {
. . .
}
The PostgresProposalRepository constructor requires a DataSource. In this case constructor injection is used to give the PostgresProposalRepository its PooledPostgresDataSource.
Now, consider the Application layer that requires the use of the PostgresProposalRepository:
// Application
package matching.application.proposal;
import matching.model.proposal.ProposalRepository;
public class ProposalCommands {
private ProposalRepository repository; public ProposalCommands(ProposalRepository repository) { this.repository = repository; } . . . }
Finally, here’s an example of a service bootstrap initializing the DataSource, PostgresProposalRepository, and the ProposalCommands instances. The bootstrap is the main(), which instantiates the Matching service and initializes it:
public class MatchingService { private DataSource dataSource; private ProposalCommands proposalCommands; private ProposalRepository proposalRepository;
public static void main(String args[]) {
var matchingService = new MatchingService();
matchingService.initialize();
. . .
}
. . .
void initialize() { this.dataSource = new PooledPostgresDataSource(new DataSourceConfiguration()); this.proposalRepository = new ProposalRepository(this.dataSource); this.proposalCommands = new ProposalCommands(this.proposalRepository); . . . } }
Some may wonder how mocks can be used to test the MatchingService’s method initialize(). After all, there are no parameters to make test mocking possible. The answer is, mocks are purposely not used. It’s extremely important that the fully expected and real production objects are initialized properly. To do so, an integration test is used to test initialize() rather than a mocked unit test. Use Testcontainers for this and all real mechanism tests, including PooledPostgresDataSource and PostgresProposalRepository, which both must be tested against their respective technical mechanisms. The bootstrap is run only in production and any UAT and thus need not be mocked for tests that directly depend on it (there won’t be any) and that must be fast. There can be literally one test for the bootstrap and it can be run infrequently if it is very slow, which it will likely not be, given the use of Testcontainers.
There you have it, Dependency Injection using constructor injection at it’s finest; the finest being the simplest. A typical DDD strategic modeling Bounded Context will tend to be smallish. In this case there are only a few Aggregate types in the Matching Context. The entire start up is just a few more initializations away from service ready to roll.
Seriously, why do you need an Inversion of Control Container?
Summary
I’ve offered an overview of my intentions in conveying my concerns about IoCC+DI, outlined the problems that are common when emphasizing technology over business capabilities, and warned against the mentality that IoCC+DI is programming and that programming is IoCC+DI. If software developers are not careful, IoCC+DI will “infect” their thinking and lead them astray from good software modeling practices. DI is possible without the use of IoCC, and is so simple when using uncomplicated programming language facilities. If you chose to use IoCC+DI anyway, consider the implications, but always use constructor-based DI.
Bottom line: never trample on business concerns by overemphasizing technical mechanisms.