diff --git a/PhysicsAnalysis/JetTagging/JetHitAssociation/CMakeLists.txt b/PhysicsAnalysis/JetTagging/JetHitAssociation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..15d0938c1e8ed604987cff48adb021d605636be9
--- /dev/null
+++ b/PhysicsAnalysis/JetTagging/JetHitAssociation/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+# Declare the package name:
+atlas_subdir( JetHitAssociation )
+
+# External dependencies:
+find_package( ROOT COMPONENTS Core TVector3 )
+
+atlas_add_library( JetHitAssociationLib
+                   src/*.h
+                   INTERFACE
+                   PUBLIC_HEADERS JetHitAssociation
+                   LINK_LIBRARIES GaudiKernel AthenaBaseComps TrkValInterfaces xAODEventInfo AthContainers xAODJet xAODTracking  InDetEventTPCnv )
+
+# Component(s) in the package:
+atlas_add_component( JetHitAssociation
+                     src/*.cxx
+                     src/components/*.cxx
+                     INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+                     LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps EventInfo EventPrimitives GaudiKernel JetHitAssociationLib )
+
+atlas_install_joboptions( share/*.py )
diff --git a/PhysicsAnalysis/JetTagging/JetHitAssociation/JetHitAssociation/JetHitAssociation.h b/PhysicsAnalysis/JetTagging/JetHitAssociation/JetHitAssociation/JetHitAssociation.h
new file mode 100644
index 0000000000000000000000000000000000000000..65549157081224694d7f29295d7156ded870c606
--- /dev/null
+++ b/PhysicsAnalysis/JetTagging/JetHitAssociation/JetHitAssociation/JetHitAssociation.h
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef JETHITASSOCIATION_H
+#define JETHITASSOCIATION_H
+
+// STL includes
+#include <string>
+
+// FrameWork includes
+#include "AthenaBaseComps/AthAlgorithm.h"
+
+// Containers
+#include "xAODJet/JetContainer.h"
+#include "xAODTracking/VertexContainer.h"
+#include "xAODTracking/TrackMeasurementValidationContainer.h"
+#include "xAODTracking/TrackMeasurementValidationAuxContainer.h"
+
+// Read and write handles
+#include "StoreGate/ReadHandleKey.h"
+#include "StoreGate/WriteHandleKey.h"
+
+class JetHitAssociation : public AthAlgorithm
+{
+  public:
+    /// Constructor with parameters: 
+    JetHitAssociation (const std::string& name,
+                       ISvcLocator* svcloc);
+
+
+    virtual StatusCode initialize() override;
+    virtual StatusCode finalize() override;
+    virtual StatusCode execute() override;
+
+
+  private:
+    // functions
+    StatusCode saveHits(const std::vector<const xAOD::Jet*> &jets, 
+	 		const xAOD::Vertex* const vertex,
+	 		SG::ReadHandle<xAOD::TrackMeasurementValidationContainer> &hits,
+			const SG::WriteHandle<xAOD::TrackMeasurementValidationContainer> &writeHandle,
+			unsigned long long int &nStoredHits,
+			unsigned long long int &nTotalHits);
+    
+    // Read and write handles
+    SG::ReadHandleKey<xAOD::JetContainer> m_jetCollectionName;
+    SG::ReadHandleKey<xAOD::VertexContainer> m_vertexCollectionName;
+    SG::ReadHandleKey<xAOD::TrackMeasurementValidationContainer>  m_inputPixHitCollectionName;
+    SG::ReadHandleKey<xAOD::TrackMeasurementValidationContainer>  m_inputSCTHitCollectionName;
+    SG::WriteHandleKey<xAOD::TrackMeasurementValidationContainer> m_outputPixHitCollectionName;
+    SG::WriteHandleKey<xAOD::TrackMeasurementValidationContainer> m_outputSCTHitCollectionName;
+    
+    // algorithm parameters, counters
+    float m_jetPtThreshold;
+    float m_dRmatchHitToJet;
+    unsigned long long int m_nStoredPixHits;
+    unsigned long long int m_nStoredSCTHits;
+    unsigned long long int m_nTotalPixHits;
+    unsigned long long int m_nTotalSCTHits;
+    
+};
+
+#endif
diff --git a/PhysicsAnalysis/JetTagging/JetHitAssociation/share/jetHitAssociation_config.py b/PhysicsAnalysis/JetTagging/JetHitAssociation/share/jetHitAssociation_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..b73935b8e49578ce4233489d06c7b6862ccd9d44
--- /dev/null
+++ b/PhysicsAnalysis/JetTagging/JetHitAssociation/share/jetHitAssociation_config.py
@@ -0,0 +1,29 @@
+# For using GeV units
+import AthenaCommon.SystemOfUnits as Units
+
+# Needed in order that PixelPrepDataToxAOD does not generate a potential memory leak error. 
+import InDetRecExample.TrackingCommon as TrackingCommon
+
+# We need to add this algorithm to get the TrackMeasurementValidationContainer
+from InDetPrepRawDataToxAOD.InDetPrepRawDataToxAODConf import PixelPrepDataToxAOD, SCT_PrepDataToxAOD
+#
+# Getting the Pixel cluster container is more complicated than geting the SCT cluster container 
+# You MUST minimally, have these items - a 'name' and this ClusterSplitProbabilityName or you get a memory leak ERROR
+xAOD_PixelPrepDataToxAOD = PixelPrepDataToxAOD( name = "xAOD_PixelPrepDataToxAOD", 
+                                                ClusterSplitProbabilityName = TrackingCommon.pixelClusterSplitProbName() )
+
+from AthenaCommon.AlgSequence import AlgSequence
+algSeq = AlgSequence()
+
+algSeq += xAOD_PixelPrepDataToxAOD
+algSeq += SCT_PrepDataToxAOD('SCT_PrepDataToxAOD')
+
+from JetHitAssociation.JetHitAssociationConf import JetHitAssociation
+jetHitAssociation = JetHitAssociation('JetHitAssociation')
+jetHitAssociation.jetCollectionName = "AntiKt4EMPFlowJets"
+jetHitAssociation.jetPtThreshold = 300 * Units.GeV
+jetHitAssociation.dRmatchHitToJet = 0.4
+#jetHitAssociation.OutputLevel = DEBUG
+
+# Add algorithm to sequence
+algSeq += jetHitAssociation
\ No newline at end of file
diff --git a/PhysicsAnalysis/JetTagging/JetHitAssociation/src/JetHitAssociation.cxx b/PhysicsAnalysis/JetTagging/JetHitAssociation/src/JetHitAssociation.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ed6a9a65eaf4289e80361aa2b05f661aafad0894
--- /dev/null
+++ b/PhysicsAnalysis/JetTagging/JetHitAssociation/src/JetHitAssociation.cxx
@@ -0,0 +1,198 @@
+/*
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "JetHitAssociation/JetHitAssociation.h"
+
+//Include some helpful ROOT objects here.
+#include <TVector3.h>
+
+// Constructor
+JetHitAssociation::JetHitAssociation(const std::string& name, ISvcLocator* svcloc)
+  : AthAlgorithm(name, svcloc),
+    m_nStoredPixHits(0),
+    m_nStoredSCTHits(0),
+    m_nTotalPixHits(0),
+    m_nTotalSCTHits(0) {
+    
+  declareProperty("jetCollectionName", m_jetCollectionName = "AntiKt4EMPFlowJets", 
+    		  "Jet collection that will be used for matching the Hits");
+  declareProperty("vertexCollectionName", m_vertexCollectionName = "PrimaryVertices", 
+    		  "Name of PV collection. Default is PrimaryVertices");
+  declareProperty("outputPixHitCollectionName", m_outputPixHitCollectionName = "JetAssociatedPixelClusters", 
+    		  "Name of output pixel hit collection. JetAssociatedPixelClusters is default.");
+  declareProperty("outputSCTHitCollectionName", m_outputSCTHitCollectionName = "JetAssociatedSCTClusters", 
+    		  "Name of output SCT hit collection. JetAssociatedSCTClusters is default.");
+  declareProperty("inputPixHitCollectionName", m_inputPixHitCollectionName = "PixelClusters", 
+    		  "Name of input pixel hit collection. PixelClusters is default");
+  declareProperty("inputSCTHitCollectionName", m_inputSCTHitCollectionName = "SCT_Clusters", 
+    		  "Name of input SCT hit collection. SCT_Clusters is default");
+  declareProperty("jetPtThreshold", m_jetPtThreshold = 300000.0, 
+    		  "Hits are saved only if they match to jets with pT > jetPtThreshold [in MeV]");
+  declareProperty("dRmatchHitToJet", m_dRmatchHitToJet = 0.4, 
+    		  "The radius used for matching hits to jets");
+		  
+  declare(m_outputPixHitCollectionName);
+  declare(m_outputSCTHitCollectionName);	  
+}
+
+StatusCode JetHitAssociation::initialize() {
+
+  ATH_CHECK( AthAlgorithm::initialize() );
+  
+  ATH_MSG_DEBUG("jetCollectionName           = " << m_jetCollectionName);
+  ATH_MSG_DEBUG("vertexCollectionName        = " << m_vertexCollectionName);
+  ATH_MSG_DEBUG("inputPixHitCollectionName   = " << m_inputPixHitCollectionName);
+  ATH_MSG_DEBUG("inputSCTHitCollectionName   = " << m_inputSCTHitCollectionName);
+  ATH_MSG_DEBUG("outputPixHitCollectionName  = " << m_outputPixHitCollectionName);
+  ATH_MSG_DEBUG("outputSCTHitCollectionName  = " << m_outputSCTHitCollectionName);
+  ATH_MSG_DEBUG("jetPtThreshold [MeV]         = " << m_jetPtThreshold);
+  ATH_MSG_DEBUG("dRmatchHitToJet             = " << m_dRmatchHitToJet);
+  
+  ATH_CHECK(m_inputPixHitCollectionName.initialize());
+  ATH_CHECK(m_inputSCTHitCollectionName.initialize());
+  ATH_CHECK(m_jetCollectionName.initialize());
+  ATH_CHECK(m_vertexCollectionName.initialize());
+  ATH_CHECK(m_outputPixHitCollectionName.initialize());
+  ATH_CHECK(m_outputSCTHitCollectionName.initialize());
+
+  ATH_MSG_DEBUG("JetHitAssociation: Initialized");
+  
+  return StatusCode::SUCCESS;
+}
+
+
+// Function for saving hits matched to jets
+StatusCode JetHitAssociation::execute() {
+  
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  
+  // All jets
+  SG::ReadHandle<xAOD::JetContainer> jetReadHandle(m_jetCollectionName, ctx);
+  if ( !jetReadHandle.isValid() ) {
+    ATH_MSG_ERROR("Failed to retrieve jet container with key " << m_jetCollectionName.key() );
+    return StatusCode::FAILURE;
+  }
+  
+  // Keep only jets above threashold
+  std::vector<const xAOD::Jet*> jets;
+  jets.reserve( jetReadHandle->size() );
+  for (const xAOD::Jet *jet : *jetReadHandle) {
+    if (jet->pt() > m_jetPtThreshold) jets.push_back(jet);
+  }
+  
+// Create write handles
+  SG::WriteHandle<xAOD::TrackMeasurementValidationContainer> outputPixHits(m_outputPixHitCollectionName, ctx);
+  SG::WriteHandle<xAOD::TrackMeasurementValidationContainer> outputSCTHits(m_outputSCTHitCollectionName, ctx);
+
+  
+  // Vertex collection
+  SG::ReadHandle<xAOD::VertexContainer> vertexReadHandle(m_vertexCollectionName, ctx);
+  if ( !vertexReadHandle.isValid() ) {
+    ATH_MSG_ERROR("Failed to retrieve PrimaryVertices container" );
+    return StatusCode::FAILURE;
+  }
+  
+  // Find primary vertex
+  const xAOD::Vertex *primVtx = nullptr;
+  for (const xAOD::Vertex *vertex : *vertexReadHandle) {
+    if (vertex->vertexType() == xAOD::VxType::PriVtx) { 
+      primVtx = vertex;
+      break;
+    }
+  }
+  
+  // Read pixel hits
+  SG::ReadHandle<xAOD::TrackMeasurementValidationContainer> pixHitReadHandle(m_inputPixHitCollectionName, ctx);
+  if ( !pixHitReadHandle.isValid() ) {
+    ATH_MSG_ERROR("Failed to retrieve pixel hit container with key " << m_inputPixHitCollectionName);
+    return StatusCode::FAILURE;
+  }
+  
+  // Read SCT hits
+  SG::ReadHandle<xAOD::TrackMeasurementValidationContainer> sctHitReadHandle(m_inputSCTHitCollectionName, ctx);
+  if ( !sctHitReadHandle.isValid() ) {
+    ATH_MSG_ERROR("Failed to retrieve SCT hit container with key " << m_inputSCTHitCollectionName);
+    return StatusCode::FAILURE;
+  }
+  
+  // These subroutines check for viable jets and write out hits if any exist. 
+  // A JetAssociatedPixelCluster or JetAssociatedSCTCluster container is created for each event regardless
+  ATH_CHECK( saveHits(jets, primVtx, pixHitReadHandle, outputPixHits, m_nStoredPixHits, m_nTotalPixHits) );
+  ATH_CHECK( saveHits(jets, primVtx, sctHitReadHandle, outputSCTHits, m_nStoredSCTHits, m_nTotalSCTHits) );
+  
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode JetHitAssociation::saveHits(const std::vector<const xAOD::Jet*> &jets, 
+				       const xAOD::Vertex* const vertex,
+				       SG::ReadHandle<xAOD::TrackMeasurementValidationContainer> &hits,
+				       const SG::WriteHandle<xAOD::TrackMeasurementValidationContainer> &outputHits,
+				       unsigned long long int &nStoredHits,
+				       unsigned long long int &nTotalHits) {
+
+  // Collection where output hits are stored
+  auto outputHitCollection = std::make_unique<xAOD::TrackMeasurementValidationContainer>();
+  auto outputHitCollectionAux = std::make_unique<xAOD::TrackMeasurementValidationAuxContainer>();
+  outputHitCollection->setStore(outputHitCollectionAux.get());
+    
+    
+  // if there are no jets you need to skip this loop but you still need to write out the 
+  // if the vertex is a null pointer (no primary vertex in the event) do the same
+  // JetAssociatedSCTClusters and JetAssociatedPixelClusters
+  if (!jets.empty() && vertex!=nullptr) {
+
+    // Get the x,y,z of the primary vertex
+    TVector3 PVposition(vertex->x(), vertex->y(), vertex->z());   
+    ATH_MSG_DEBUG("JetHitAssociation: PrimaryVertex Z Position = " << PVposition.Z());
+
+    // Loop over hits
+    for (const xAOD::TrackMeasurementValidation *hit : *hits) {
+      // Get pixel globalX,Y,Z
+      float x = hit->globalX();
+      float y = hit->globalY();
+      float z = hit->globalZ();
+
+      // Put the x,y,z in a TVector3
+      TVector3 hitPosition(x,y,z);
+
+      // Correct for the PV position
+      hitPosition = hitPosition - PVposition;
+
+      // Loop over jets
+      for (const xAOD::Jet *jet : jets) {
+	// Calculate dR(hit,jet)
+	float dR = hitPosition.DeltaR(jet->p4().Vect());
+      
+	// if the dR is smaller than the dR association threshold save the hit
+	if (dR < m_dRmatchHitToJet) {
+	  outputHitCollection->push_back(std::make_unique<xAOD::TrackMeasurementValidation>(*hit));
+	  *outputHitCollection->back() = *hit; //Without this line. entries are filled with zero instead of the correct values
+	  ++nStoredHits;
+	  break;
+	}
+      }   
+    }
+  }  
+  // count the total number of hits run
+  nTotalHits += hits->size(); 
+    
+  // Write output container
+  if ( ! outputHits.put( std::move(outputHitCollection), std::move(outputHitCollectionAux) ) ) {
+    ATH_MSG_ERROR("Could not write output hit containers");
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode JetHitAssociation::finalize() {
+
+  ATH_MSG_DEBUG("JetHitAssociation: Total number of Pix Hits tested = " << m_nTotalPixHits);
+  ATH_MSG_DEBUG("JetHitAssociation: Total number of Pix Hits Stored = " << m_nStoredPixHits);
+  ATH_MSG_DEBUG("JetHitAssociation: Total number of SCT Hits tested = " << m_nTotalSCTHits);
+  ATH_MSG_DEBUG("JetHitAssociation: Total number of SCT Hits Stored = " << m_nStoredSCTHits);
+  
+  return StatusCode::SUCCESS;
+}
diff --git a/PhysicsAnalysis/JetTagging/JetHitAssociation/src/components/JetHitAssociation_entries.cxx b/PhysicsAnalysis/JetTagging/JetHitAssociation/src/components/JetHitAssociation_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..95dc9f6cf29b08f390c4ca63cca62a0ce64153e2
--- /dev/null
+++ b/PhysicsAnalysis/JetTagging/JetHitAssociation/src/components/JetHitAssociation_entries.cxx
@@ -0,0 +1,3 @@
+#include "JetHitAssociation/JetHitAssociation.h"
+
+DECLARE_COMPONENT( JetHitAssociation )
diff --git a/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/python/BTaggingFlags.py b/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/python/BTaggingFlags.py
index bdafa3dd308f5b27b7da12f64192222864b3f41b..38119c60bb648415edbb3bb491cf0b44f251e065 100755
--- a/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/python/BTaggingFlags.py
+++ b/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/python/BTaggingFlags.py
@@ -24,6 +24,8 @@ class _BTaggingFlags:
 
     btaggingESDList = [ ]
 
+    DoJetHitAssociation = False
+
     def __init__ (self):
 
       self.btaggingAODList = list()
diff --git a/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/share/BTaggingReconstructionOutputAODList_jobOptions.py b/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/share/BTaggingReconstructionOutputAODList_jobOptions.py
index cf2e3604082cb83dabbcb497404b7acc6ed30c2a..5bad5737826fb6d0bd40671d65b0c704663ae30f 100644
--- a/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/share/BTaggingReconstructionOutputAODList_jobOptions.py
+++ b/PhysicsAnalysis/JetTagging/JetTagAlgs/BTagging/share/BTaggingReconstructionOutputAODList_jobOptions.py
@@ -28,6 +28,12 @@ if len(BTaggingAODList) == 0:
 #    BTaggingAODList += ["xAOD::BTagVertexAuxContainer#*"]
 #    BTaggingAODList += ["xAOD::VertexContainer#BTagging*"]
 #    BTaggingAODList += ["xAOD::VertexAuxContainer#BTagging*"]
+
+if BTaggingFlags.DoJetHitAssociation:
+    BTaggingAODList += ['xAOD::TrackMeasurementValidationContainer#JetAssociatedPixelClusters',
+                        'xAOD::TrackMeasurementValidationAuxContainer#JetAssociatedPixelClustersAux.']
+    BTaggingAODList += ['xAOD::TrackMeasurementValidationContainer#JetAssociatedSCTClusters',
+                        'xAOD::TrackMeasurementValidationAuxContainer#JetAssociatedSCTClustersAux.']
     
 printfunc ("#BTAG# ESD output container list: " + str(BTaggingESDList))
 printfunc ("#BTAG# AOD output container list: " + str(BTaggingAODList))
diff --git a/Reconstruction/RecExample/RecExCommon/share/CombinedRec_config.py b/Reconstruction/RecExample/RecExCommon/share/CombinedRec_config.py
index 79f8043311d194094c2652fc26ee8910f9c87b03..121797c1d4cbaab849b280d9b332d810d929eef7 100755
--- a/Reconstruction/RecExample/RecExCommon/share/CombinedRec_config.py
+++ b/Reconstruction/RecExample/RecExCommon/share/CombinedRec_config.py
@@ -157,6 +157,14 @@ if (jetOK or rec.readESD()) and rec.doBTagging() and  DetFlags.ID_on() and DetFl
         Configurable.configurableRun3Behavior=0
     pass
 
+# Hits associated with high-pt jets for trackless b-tagging
+from BTagging.BTaggingFlags import BTaggingFlags
+if (jetOK or rec.readESD()) and DetFlags.ID_on() and rec.doWriteAOD() and BTaggingFlags.DoJetHitAssociation:
+    try:
+        include("JetHitAssociation/jetHitAssociation_config.py")
+    except Exception:
+        treatException("Could not set up jet hit association")
+
 #
 # functionality : tau identification
 #